1 /// Provides a map (i.e. key/value) tuple.
2 module memgraph.map;
3 
4 import std.string, std.conv;
5 
6 import memgraph.mgclient, memgraph.detail, memgraph.value, memgraph.enums;
7 
8 /// Sized sequence of pairs of keys and values.
9 /// Maximum possible map size allowed by Bolt protocol is `uint.max`.
10 ///
11 /// Map may contain a mixture of different types as values. A map owns all keys
12 /// and values stored in it.
13 ///
14 /// Can be used like a standard D hash map (because it is one under the hood).
15 struct Map {
16 	/// Map needs an initial capacity.
17 	@disable this();
18 
19 	/// Construct a new map from an associative array of key, value pairs.
20 	this(const ref Value[string] valueMap) {
21 		this(mg_map_make_empty(to!uint(valueMap.length)));
22 		foreach (ref key, ref value; valueMap) {
23 			immutable rc = mg_map_insert(ptr_, toStringz(key), mg_value_copy(value.ptr));
24 			assert(rc == mg_error.MG_SUCCESS);
25 		}
26 	}
27 
28 	/// Constructs a map that can hold at most `capacity` elements.
29 	/// Params: capacity = The maximum number of elements that the newly constructed
30 	///                    list can hold.
31 	this(uint capacity) {
32 		this(mg_map_make_empty(capacity));
33 	}
34 
35 	/// Create a copy of `other` map.
36 	this(inout ref Map other) {
37 		this(mg_map_copy(other.ptr));
38 	}
39 
40 	/// Create a map from a Value.
41 	this(inout ref Value value) {
42 		assert(value.type == Type.Map);
43 		this(mg_map_copy(mg_value_map(value.ptr)));
44 	}
45 
46 	this(this) {
47 		if (ptr_)
48 			ptr_ = mg_map_copy(ptr_);
49 	}
50 
51 	/// Destructor. Destroys the internal `mg_map`.
52 	@safe @nogc ~this() pure nothrow {
53 		if (ptr_ != null)
54 			mg_map_destroy(ptr_);
55 	}
56 
57 	/// Returns the value associated with the given `key`.
58 	/// If the given `key` does not exist, an empty `Value` is returned.
59 	/*
60 	ref Value opIndex(const string key) {
61 		return map_.require(key, Value());
62 	}
63 	*/
64 
65 	/// Returns the value associated with the given `key`.
66 	auto opIndex(const string key) const {
67 		return Value(mg_map_at(ptr_, toStringz(key)));
68 	}
69 
70 	/// Compares this map with `other`.
71 	/// Return: true if same, false otherwise.
72 	bool opEquals(const Map other) const {
73 		return Detail.areMapsEqual(ptr_, other.ptr);
74 	}
75 
76 	/// Compares this map with an associative array of key, value pairs.
77 	/// Return: true if same, false otherwise.
78 	bool opEquals(const ref Value[string] valueMap) const {
79 		auto other = Map(valueMap);
80 		return Detail.areMapsEqual(ptr_, other.ptr_);
81 	}
82 
83 	/// Remove given `key` from map.
84 	/// Return: true if key was removed, false otherwise.
85 	/*
86 	auto remove(const string key) {
87 		return map_.remove(key);
88 	}
89 	/// Clears the map.
90 	void clear() {
91 		map_.clear();
92 	}
93 	*/
94 
95 	/*
96 	auto opDispatch(string name, T...)(T vals) {
97 		return mixin("map_." ~ name)(vals);
98 	}
99 	*/
100 
101 	/*
102 	auto opBinary(string op)(const char[] key) if (op == "in") {
103 		return mg_map_at(ptr_, toStringz(key)) != null;
104 	}
105 	*/
106 
107 	auto opBinaryRight(string op)(const char[] key) if (op == "in") {
108 		return mg_map_at(ptr_, toStringz(key)) != null;
109 	}
110 
111 	auto length() const {
112 		return mg_map_size(ptr_);
113 	}
114 
115 	// @property auto toAA() const { return map_; }
116 
117 	/*
118 	@property @safe @nogc ref inout(Value[string]) toAA() inout pure nothrow {
119 		return map_;
120 	}
121 	*/
122 
123 	/// Return a printable string representation of this map.
124 	const (string) toString() const {
125 		assert(ptr_);
126 		immutable len = length;
127 		string ret = "{";
128 		for (uint i = 0; i < len; i++) {
129 			ret ~= Detail.convertString(mg_map_key_at(ptr_, i)) ~ ":" ~
130 					to!string(Value(mg_map_value_at(ptr_, i)));
131 			if (i < len-1)
132 				ret ~= ",";
133 		}
134 		ret ~= "}";
135 		return ret;
136 	}
137 
138 	/*
139 	ref Value opIndexOpAssign(string op)(int value, const char[] key) if (op == "=") {
140 		auto val = Value(value);
141 		immutable rc = mg_map_insert(ptr_, toStringz(key), val.ptr);
142 		assert(rc == mg_error.MG_SUCCESS);
143 		return val;
144 	}
145 	*/
146 
147 	Value opIndexAssign(T)(const T value, const string key) {
148 		auto val = Value(value);
149 		immutable rc = mg_map_insert(ptr_, toStringz(key), mg_value_copy(val.ptr));
150 		assert(rc == mg_error.MG_SUCCESS);
151 		return val;
152 	}
153 
154 	/// Checks if the map as range is empty.
155 	@property bool empty() const { return idx_ >= length; }
156 
157 	/// Returns the next element in the map range.
158 	@property auto front() const {
159 		import std.typecons : Tuple;
160 		assert(idx_ < length);
161 		return Tuple!(string, "key", Value, "value")(
162 					Detail.convertString(mg_map_key_at(ptr_, idx_)),
163 					Value(mg_map_value_at(ptr_, idx_)));
164 	}
165 
166 	/// Move to the next element in the list range.
167 	void popFront() { idx_++; }
168 
169 package:
170 	/// Create a Map using the given `mg_map`.
171 	this(mg_map *ptr) {
172 		assert(ptr != null);
173 		ptr_ = ptr;
174 	}
175 
176 	/// Create a Map from a copy of the given `mg_map`.
177 	this(const mg_map *ptr) {
178 		assert(ptr != null);
179 		this(mg_map_copy(ptr));
180 	}
181 
182 	/// Return pointer to internal mg_map.
183 	const (mg_map *) ptr() const { return ptr_; }
184 
185 private:
186 	mg_map *ptr_;
187 	uint idx_;
188 }
189 
190 unittest {
191 	import std.range.primitives : isInputRange;
192 	assert(isInputRange!Map);
193 }
194 
195 unittest {
196 	auto m1 = Map(10);
197 	m1["key1"] = 123;
198 	auto m2 = Map(10);
199 	assert(m1 != m2);
200 
201 	m2["key1"] = 456;
202 	assert(m1 != m2);
203 
204 	m1["key2"] = "Hi!";
205 	m2["key99"] = "Bok!";
206 	assert(m1 != m2);
207 }
208 
209 unittest {
210 	auto m1 = Map(10);
211 	m1["key1"] = 123;
212 	auto m2 = Map(10);
213 	assert(m1 != m2);
214 
215 	m2["key1"] = 123;
216 	assert(m1 == m2);
217 
218 	m1["key2"] = "Hi!";
219 	m2["key99"] = "Bok!";
220 	assert(m1 != m2);
221 }
222 
223 unittest {
224 	auto m = Map(32);
225 	m["answer_to_life_the_universe_and_everything"] = 42;
226 	assert("answer_to_life_the_universe_and_everything" in m);
227 	assert(m["answer_to_life_the_universe_and_everything"] == 42);
228 	assert(m.length == 1);
229 	assert(to!string(m) == "{answer_to_life_the_universe_and_everything:42}");
230 
231 	m["id"] = 0;
232 	m["age"] = 40;
233 	m["name"] = "John";
234 	m["isStudent"] = false;
235 	m["score"] = 5.0;
236 	assert(m.length == 6);
237 
238 	assert("id" in m);
239 	assert(m["id"] == 0);
240 	assert("age" in m);
241 	assert(m["age"] == 40);
242 	assert("name" in m);
243 	assert(m["name"] == "John");
244 	assert("isStudent" in m);
245 	assert(m["isStudent"] == false);
246 	assert("score" in m);
247 	assert(m["score"] == 5.0);
248 
249 	// This is a package internal method, not for public consumption.
250 	assert(m.ptr != null);
251 
252 	import std.algorithm : map;
253 	assert(m.map!(p => p.key ~ ":" ~ to!string(p.value)).join(",") ==
254 			"answer_to_life_the_universe_and_everything:42,id:0,age:40,name:John,isStudent:false,score:5");
255 
256 	auto m2 = Map(m);
257 	assert(m == m2);
258 
259 	auto v = Value(m2);
260 	assert(v == m);
261 
262 	assert(to!string(v) == to!string(m));
263 }
264 
265 unittest {
266 	Value[string] vm;
267 	vm["int"] = 42;
268 	vm["long"] = 23L;
269 	vm["double"] = 5.43210;
270 	vm["bool"] = true;
271 	vm["string"] = "Hi";
272 	assert(vm.length == 5);
273 
274 	auto m = Map(vm);
275 	foreach (k, v; vm)
276 		assert(v == m[k]);
277 	foreach (k, v; m)
278 		assert(v == vm[k]);
279 
280 	assert(m == vm);
281 }