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 }