1 /// Provides a wrapper around a `Path`. 2 module memgraph.path; 3 4 import memgraph.mgclient, memgraph.detail, memgraph.map, memgraph.value, memgraph.node, memgraph.unboundrelationship; 5 import memgraph.enums; 6 7 /// Represents a sequence of alternating nodes and relationships 8 /// corresponding to a walk in a labeled property graph. 9 /// 10 /// A path of length L consists of L + 1 nodes indexed from 0 to L, and L 11 /// unbound relationships, indexed from 0 to L - 1. Each relationship has a 12 /// direction. A relationship is said to be reversed if it was traversed in the 13 /// direction opposite of the direction of the underlying relationship in the 14 /// data graph. 15 struct Path { 16 /// Create a shallow copy of `other` path. 17 @nogc this(inout ref Path other) { 18 this(other.ptr); 19 } 20 21 /// Create a path from a Value. 22 @nogc this(inout ref Value value) { 23 assert(value.type == Type.Path); 24 this(mg_value_path(value.ptr)); 25 } 26 27 /// Return a printable string representation of this path. 28 string toString() const { 29 import std.array : appender; 30 import std.conv : to; 31 auto str = appender!string; 32 foreach (i; 0..length+1) { 33 auto node = getNodeAt(to!uint(i)); 34 str.put(to!string(node)); 35 str.put("\n"); 36 if (i < length) { 37 auto rel = getRelationshipAt(to!uint(i)); 38 if (isReversedRelationshipAt(i)) { 39 str.put(" <-"); 40 str.put(to!string(rel)); 41 str.put("- "); 42 } else { 43 str.put(" -"); 44 str.put(to!string(rel)); 45 str.put("-> "); 46 } 47 } 48 } 49 return str.data; 50 } 51 52 /// Compares this path with `other`. 53 /// Return: true if same, false otherwise. 54 @nogc auto opEquals(const ref Path other) const { 55 return Detail.arePathsEqual(ptr_, other.ptr); 56 } 57 58 /// Return the hash code for this path. 59 size_t toHash() const nothrow @safe { 60 return cast(ulong)ptr_; 61 } 62 63 /// Returns the path length. 64 /// Length of the path is number of edges. 65 @nogc auto length() const { 66 return mg_path_length(ptr_); 67 } 68 69 /// Returns the vertex at the given `index`. 70 /// `index` should be less than or equal to length of the path. 71 @nogc auto getNodeAt(uint index) const { 72 assert(index <= length); 73 const auto vertex_ptr = mg_path_node_at(ptr_, index); 74 assert(vertex_ptr != null); 75 return Node(vertex_ptr); 76 } 77 78 /// Returns the edge at the given `index`. 79 /// `index` should be less than length of the path. 80 @nogc auto getRelationshipAt(uint index) const { 81 assert(index < length); 82 const edge_ptr = mg_path_relationship_at(ptr_, index); 83 assert(edge_ptr != null); 84 return UnboundRelationship(edge_ptr); 85 } 86 87 /// Returns the orientation of the edge at the given `index`. 88 /// `index` should be less than length of the path. 89 /// Return: True if the edge is reversed, false otherwise. 90 @nogc auto isReversedRelationshipAt(uint index) const { 91 const is_reversed = mg_path_relationship_reversed_at(ptr_, index); 92 assert(is_reversed != -1); 93 return is_reversed == 1; 94 } 95 96 package: 97 /// Create a Path using the given `mg_path` pointer. 98 @nogc this(const mg_path *ptr) { 99 assert(ptr != null); 100 ptr_ = ptr; 101 } 102 103 /// Return pointer to internal `mg_path`. 104 @nogc auto ptr() inout { return ptr_; } 105 106 private: 107 const mg_path *ptr_; 108 } // struct Path 109 110 unittest { 111 import testutils : connectContainer, createTestData, deleteTestData; 112 import memgraph : Client, Type, Value, Node, Relationship, List; 113 import std.conv : to; 114 115 auto client = connectContainer(); 116 assert(client); 117 118 deleteTestData(client); 119 120 createTestData(client); 121 122 auto res = client.execute( 123 "MATCH p = ()-[*3]->() RETURN p;"); 124 assert(res, client.error); 125 foreach (c; res) { 126 assert(c[0].type == Type.Path); 127 128 auto path = to!Path(c[0]); 129 130 auto p2 = path; 131 const p3 = c[0]; 132 assert(p2 == p3); 133 auto p4 = Path(path); 134 assert(p4 == path); 135 136 foreach (i; 0..path.length) { 137 assert(p2.isReversedRelationshipAt(to!uint(i)) == 138 p4.isReversedRelationshipAt(to!uint(i))); 139 } 140 141 assert(to!string(path) == 142 `["Person", "Entrepreneur"] {age:40, id:0, isStudent:false, name:John, score:5} 143 -[IS_MANAGER]-> ["Person", "Entrepreneur"] {age:50, id:2, isStudent:false, name:Peter, score:4} 144 -[IS_MANAGER]-> ["Person", "Entrepreneur"] {age:20, id:1, isStudent:true, name:Valery, score:5} 145 -[IS_MANAGER]-> ["Person", "Entrepreneur"] {age:25, id:4, isStudent:true, name:Oløf, score:10} 146 `, to!string(path)); 147 148 immutable auto expectedNames = [ "John", "Peter", "Valery", "Oløf" ]; 149 150 foreach (i; 0..path.length+1) { 151 const n = path.getNodeAt(to!uint(i)); 152 153 auto p = n.properties; 154 assert(to!string(p["name"]) == expectedNames[i], to!string(p["name"])); 155 156 const n2 = n; 157 assert(n2 == n); 158 159 if (i < path.length) { 160 auto r = path.getRelationshipAt(to!uint(i)); 161 const r2 = r; 162 assert(r2 == r); 163 const r4 = UnboundRelationship(r); 164 assert(r4 == r); 165 const r5 = UnboundRelationship(r2); 166 assert(r5 == r); 167 168 assert(to!string(r) == "[" ~ r.type ~ "]"); 169 170 assert(r5.id == r.id); 171 assert(r5.type == r.type); 172 assert(r5.properties == r.properties); 173 174 assert(to!string(r5) == to!string(r)); 175 } 176 } 177 assert(path.ptr != null); 178 179 const p6 = path; 180 const p7 = Path(p6); 181 assert(p7 == path); 182 } 183 } 184 185 unittest { 186 import testutils : connectContainer, createTestData, deleteTestData; 187 import memgraph : Client, Type, Value, Node, Relationship, List; 188 import std.conv : to; 189 190 auto client = connectContainer(); 191 assert(client); 192 193 deleteTestData(client); 194 195 createTestData(client); 196 197 auto res = client.execute( 198 "MATCH p = ()<-[*3]-() RETURN p;"); 199 assert(res, client.error); 200 foreach (c; res) { 201 assert(c[0].type == Type.Path); 202 203 auto path = to!Path(c[0]); 204 205 assert(cast(ulong)path.ptr == path.toHash); 206 207 auto p2 = path; 208 const p3 = c[0]; 209 assert(p2 == p3); 210 auto p4 = Path(path); 211 assert(p4 == path); 212 213 foreach (i; 0..path.length) { 214 assert(p2.isReversedRelationshipAt(to!uint(i)) == 215 p4.isReversedRelationshipAt(to!uint(i))); 216 } 217 218 assert(to!string(path) == 219 `["Person", "Entrepreneur"] {age:25, id:4, isStudent:true, name:Oløf, score:10} 220 <-[IS_MANAGER]- ["Person", "Entrepreneur"] {age:20, id:1, isStudent:true, name:Valery, score:5} 221 <-[IS_MANAGER]- ["Person", "Entrepreneur"] {age:50, id:2, isStudent:false, name:Peter, score:4} 222 <-[IS_MANAGER]- ["Person", "Entrepreneur"] {age:40, id:0, isStudent:false, name:John, score:5} 223 `, to!string(path)); 224 225 immutable auto expectedNames = [ "Oløf", "Valery", "Peter", "John" ]; 226 227 foreach (i; 0..path.length+1) { 228 const n = path.getNodeAt(to!uint(i)); 229 230 auto p = n.properties; 231 assert(to!string(p["name"]) == expectedNames[i], to!string(p["name"])); 232 233 const n2 = n; 234 assert(n2 == n); 235 236 if (i < path.length) { 237 auto r = path.getRelationshipAt(to!uint(i)); 238 const r2 = r; 239 assert(r2 == r); 240 const r4 = UnboundRelationship(r); 241 assert(r4 == r); 242 const r5 = UnboundRelationship(r2); 243 assert(r5 == r); 244 245 assert(to!string(r) == "[" ~ r.type ~ "]"); 246 247 assert(r5.id == r.id); 248 assert(r5.type == r.type); 249 assert(r5.properties == r.properties); 250 251 assert(to!string(r5) == to!string(r)); 252 } 253 } 254 assert(path.ptr != null); 255 256 const p6 = path; 257 const p7 = Path(p6); 258 assert(p7 == path); 259 } 260 }