1 /// Provides a node wrapper.
2 module memgraph.node;
3 
4 import memgraph.mgclient, memgraph.detail, memgraph.map;
5 import memgraph.value, memgraph.enums;
6 
7 import std.conv;
8 
9 /// Represents a node from a labeled property graph.
10 ///
11 /// Consists of a unique identifier (withing the scope of its origin graph), a
12 /// list of labels and a map of properties. A node owns its labels and
13 /// properties.
14 ///
15 /// Maximum possible number of labels allowed by Bolt protocol is `uint.max`.
16 struct Node {
17   /// View of the node's labels.
18   struct Labels {
19     /// Returns the number of labels in the node.
20     @nogc size_t size() const {
21       return mg_node_label_count(node_);
22     }
23 
24     /// Return node's label at the `index` position.
25     @nogc string opIndex(int index) const {
26       return Detail.convertString(mg_node_label_at(node_, index));
27     }
28 
29     /// Checks if the range is empty.
30     @nogc bool empty() const { return idx_ >= size(); }
31 
32     /// Returns the next element in the range.
33     @nogc auto front() const {
34       assert(idx_ < size());
35       return Detail.convertString(mg_node_label_at(node_, idx_));
36     }
37 
38     /// Move to the next element in the range.
39     @nogc void popFront() { idx_++; }
40 
41     @nogc bool opEquals(const string[] labels) const {
42       if (labels.length != size())
43         return false;
44       // Note: having @nogc is more important than the implicit conversion warning here
45       foreach (uint idx, label; labels) {
46         if (label != Detail.convertString(mg_node_label_at(node_, idx)))
47           return false;
48       }
49       return true;
50     }
51 
52     /// Return the hash code for this label.
53     size_t toHash() const nothrow @safe {
54       return cast(ulong)node_;
55     }
56 
57   private:
58     @nogc this(const mg_node *node) {
59       assert(node != null);
60       node_ = node;
61     }
62     const mg_node *node_;
63     uint idx_;
64   } // struct Labels
65 
66   /// Return a printable string representation of this node.
67   string toString() const {
68     import std.array : appender;
69     auto str = appender!string;
70     str.put(to!string(labels));
71     str.put(" ");
72     str.put(to!string(properties));
73     return str.data;
74   }
75 
76   /// Create a shallow copy of the given `node`.
77   @nogc this(inout ref Node other) {
78     this(other.ptr_);
79   }
80 
81   /// Create a node from a Value.
82   @nogc this(inout ref Value value) {
83     assert(value.type == Type.Node);
84     this(mg_value_node(value.ptr));
85   }
86 
87   /// Returns the ID of this node.
88   @nogc auto id() inout {
89     return mg_node_id(ptr_);
90   }
91 
92   /// Returns the labels belonging to this node.
93   @nogc auto labels() inout { return Labels(ptr_); }
94 
95   /// Returns the property map belonging to this node.
96   @nogc auto properties() inout { return Map(mg_node_properties(ptr_)); }
97 
98   /// Comparison operator.
99   @nogc bool opEquals(const ref Node other) const {
100     return Detail.areNodesEqual(ptr_, other.ptr_);
101   }
102 
103   /// Return the hash code for this node.
104   size_t toHash() const nothrow @safe {
105     return cast(ulong)ptr_;
106   }
107 
108 package:
109   /// Create a Node from a copy of the given `mg_node`.
110   @nogc this(const mg_node *p) {
111     assert(p != null);
112     ptr_ = p;
113   }
114 
115   @nogc auto ptr() inout { return ptr_; }
116 
117 private:
118   /// Pointer to `mg_node` instance.
119   const mg_node *ptr_;
120 } // struct Node
121 
122 unittest {
123   import testutils : startContainer;
124   startContainer();
125 }
126 
127 unittest {
128   import testutils;
129   import memgraph;
130   import std.algorithm, std.conv, std.range;
131 
132   import std.stdio;
133 
134   auto client = connectContainer();
135   assert(client);
136 
137   createTestIndex(client);
138 
139   deleteTestData(client);
140 
141   createTestData(client);
142 
143   auto result = client.execute("MATCH (n) RETURN n;");
144   assert(result, client.error);
145   assert(!result.empty());
146   // TODO: this invalidates the result set
147   // assert(result.count == 5);
148   auto value = result.front;
149 
150   assert(value[0].type() == Type.Node, to!string(value[0].type()));
151 
152   auto node = to!Node(value[0]);
153 
154   auto labels = node.labels();
155 
156   assert(node.id() >= 0);
157 
158   immutable auto expectedLabels = [ "Person", "Entrepreneur" ];
159 
160   assert(labels.size() == 2);
161   assert(labels[0] == expectedLabels[0]);
162   assert(labels[1] == expectedLabels[1]);
163 
164   assert([] != labels);
165   assert([ "Nope", "x" ] != labels);
166   assert(expectedLabels == labels);
167   assert(expectedLabels.join(":") == labels.join(":"));
168 
169   assert(cast(ulong)node.ptr == labels.toHash);
170 
171   const other = Node(node);
172   assert(other == node);
173 
174   const auto props = node.properties();
175   assert(props.length == 5);
176   assert(props["id"] == 0);
177   assert(props["age"] == 40);
178   assert(props["name"] == "John");
179   assert(props["isStudent"] == false);
180   assert(props["score"] == 5.0);
181 
182   assert(to!string(node) == `["Person", "Entrepreneur"] {age:40, id:0, isStudent:false, name:John, score:5}`,
183             to!string(node));
184 
185   const otherProps = props;
186   assert(otherProps == props);
187 
188   // this is a package internal method
189   assert(node.ptr != null);
190 
191   assert(cast(ulong)node.ptr == node.toHash);
192 }