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 of node `node`.
20 		size_t size() const {
21 			assert(node_ != null);
22 			return mg_node_label_count(node_);
23 		}
24 
25 		/// Return node's label at the `index` position.
26 		string opIndex(int index) const {
27 			assert(node_ != null);
28 			return Detail.convertString(mg_node_label_at(node_, index));
29 		}
30 
31 		/// Checks if the range is empty.
32 		bool empty() const {
33 			return idx_ >= size();
34 		}
35 
36 		/// Returns the next element in the range.
37 		auto front() const {
38 			assert(idx_ < size());
39 			return Detail.convertString(mg_node_label_at(node_, idx_));
40 		}
41 
42 		/// Move to the next element in the range.
43 		void popFront() {
44 			idx_++;
45 		}
46 
47 		bool opEquals(const string[] labels) const {
48 			if (labels.length != size())
49 				return false;
50 			foreach (idx, label; labels) {
51 				if (label != Detail.convertString(mg_node_label_at(node_, to!uint(idx))))
52 					return false;
53 			}
54 			return true;
55 		}
56 
57 	private:
58 		this(const mg_node *node) {
59 			assert(node != null);
60 			node_ = node;
61 		}
62 		const mg_node *node_;
63 		uint idx_;
64 	}
65 
66 	/// Return a printable string representation of this node.
67 	const (string) toString() const {
68 		import std.range : join;
69 		return labels.join(":") ~ " " ~ to!string(properties());
70 	}
71 
72 	this(this) {
73 		if (ptr_)
74 			ptr_ = mg_node_copy(ptr_);
75 	}
76 
77 	/// Create a copy of the given `node`.
78 	this(inout ref Node other) {
79 		this(mg_node_copy(other.ptr_));
80 	}
81 
82 	/// Create a node from a Value.
83 	this(inout ref Value value) {
84 		assert(value.type == Type.Node);
85 		this(mg_node_copy(mg_value_node(value.ptr)));
86 	}
87 
88 	/// Returns the ID of this node.
89 	long id() const {
90 		assert(ptr_ != null);
91 		return mg_node_id(ptr_);
92 	}
93 
94 	/// Returns the labels belonging to this node.
95 	auto labels() inout { return Labels(ptr_); }
96 
97 	/// Returns the property map belonging to this node.
98 	auto properties() inout { return Map(mg_node_properties(ptr_)); }
99 
100 	/// Comparison operator.
101 	bool opEquals(const ref Node other) const {
102 		return Detail.areNodesEqual(ptr_, other.ptr_);
103 	}
104 
105 	~this() {
106 		if (ptr_)
107 			mg_node_destroy(ptr_);
108 	}
109 
110 package:
111 	/// Create a Node using the given `mg_node`.
112 	this(mg_node *p) {
113 		assert(p != null);
114 		ptr_ = p;
115 	}
116 
117 	/// Create a Node from a copy of the given `mg_node`.
118 	this(const mg_node *p) {
119 		assert(p != null);
120 		this(mg_node_copy(p));
121 	}
122 
123 	const (mg_node *) ptr() const { return ptr_; }
124 
125 private:
126 	/// Pointer to `mg_node` instance.
127 	mg_node *ptr_;
128 }
129 
130 unittest {
131 	import testutils : startContainer;
132 	startContainer();
133 }
134 
135 unittest {
136 	import testutils;
137 	import memgraph;
138 	import std.algorithm, std.conv, std.range;
139 
140 	auto client = connectContainer();
141 	assert(client);
142 
143 	createTestIndex(client);
144 
145 	deleteTestData(client);
146 
147 	createTestData(client);
148 
149 	auto result = client.execute("MATCH (n) RETURN n;");
150 	assert(result, client.error);
151 	assert(!result.empty());
152 	auto value = result.front;
153 	assert(result.count == 5);
154 
155 	assert(value[0].type() == Type.Node);
156 
157 	auto node = to!Node(value[0]);
158 
159 	auto labels = node.labels();
160 
161 	assert(node.id() >= 0);
162 
163 	immutable auto expectedLabels = [ "Person", "Entrepreneur" ];
164 
165 	assert(labels.size() == 2);
166 	assert(labels[0] == expectedLabels[0]);
167 	assert(labels[1] == expectedLabels[1]);
168 
169 	assert([] != labels);
170 	assert([ "Nope", "x" ] != labels);
171 	assert(expectedLabels == labels);
172 	assert(expectedLabels.join(":") == labels.join(":"));
173 
174 	auto other = Node(node);
175 	assert(other == node);
176 
177 	const auto props = node.properties();
178 	assert(props.length == 5);
179 	assert(props["id"] == 0);
180 	assert(props["age"] == 40);
181 	assert(props["name"] == "John");
182 	assert(props["isStudent"] == false);
183 	assert(props["score"] == 5.0);
184 
185 	assert(to!string(node) == labels.join(":") ~ " " ~ to!string(props));
186 
187 	const otherProps = props;
188 	assert(otherProps == props);
189 
190 	// this is a package internal method
191 	assert(node.ptr != null);
192 
193 	const v = Value(node);
194 	assert(v.type == Type.Node);
195 	assert(v == node);
196 	assert(node == v);
197 
198 	const v2 = Value(node);
199 	assert(v == v2);
200 }