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 }