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 }