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.
15 struct Map {
16   /// Create a shallow copy of `other` map.
17   @nogc this(inout ref Map other) {
18     this(other.ptr);
19   }
20 
21   /// Create a map from a Value.
22   @nogc this(inout ref Value value) {
23     assert(value.type == Type.Map);
24     this(mg_value_map(value.ptr));
25   }
26 
27   /// Returns the value associated with the given `key`.
28   auto opIndex(const string key) const {
29     return Value(mg_map_at(ptr_, toStringz(key)));
30   }
31 
32   /// Compares this map with `other`.
33   /// Return: true if same, false otherwise.
34   @nogc bool opEquals(const Map other) const {
35     return Detail.areMapsEqual(ptr_, other.ptr);
36   }
37 
38   /// Return the hash code for this map.
39   size_t toHash() const nothrow @safe {
40     return cast(ulong)ptr_;
41   }
42 
43   /// Checks if the map contains the given `key`.
44   /// Return: `true` if map contains `key`, `false` otherwise.
45   auto opBinaryRight(string op)(const char[] key) if (op == "in") {
46     return mg_map_at(ptr_, toStringz(key)) != null;
47   }
48 
49   /// Returns the number of key / value pairs in this map.
50   @nogc @property uint length() const { return mg_map_size(ptr_); }
51 
52   /// Return a printable string representation of this map.
53   string toString() const {
54     import std.array : appender;
55     auto str = appender!string;
56     str.put("{");
57     immutable len = length;
58     for (uint i = 0; i < len; i++) {
59       str.put(Detail.convertString(mg_map_key_at(ptr_, i)));
60       str.put(":");
61       str.put(to!string(Value(mg_map_value_at(ptr_, i))));
62       if (i < len-1)
63         str.put(", ");
64     }
65     str.put("}");
66     return str.data;
67   }
68 
69   /// Checks if the map as range is empty.
70   @nogc bool empty() const { return idx_ >= length; }
71 
72   /// Returns the next element in the map range.
73   @nogc auto front() const {
74     import std.typecons : Tuple;
75     assert(idx_ < length);
76     return Tuple!(string, "key", Value, "value")(
77                 Detail.convertString(mg_map_key_at(ptr_, idx_)),
78                 Value(mg_map_value_at(ptr_, idx_)));
79   }
80 
81   /// Move to the next element in the list range.
82   @nogc void popFront() { idx_++; }
83 
84 package:
85   /// Create a Map from a copy of the given `mg_map`.
86   @nogc this(inout mg_map *ptr) {
87     assert(ptr != null);
88     ptr_ = ptr;
89   }
90 
91   /// Return pointer to internal `mg_map`.
92   @nogc auto ptr() inout { return ptr_; }
93 
94 private:
95   const mg_map *ptr_;
96   uint idx_;
97 } // struct Map
98 
99 unittest {
100   import std.range.primitives : isInputRange;
101   assert(isInputRange!Map);
102 }
103 
104 unittest {
105   import testutils : connectContainer;
106   import std.algorithm : count;
107   import std.conv : to;
108   import memgraph.local_date_time;
109 
110   auto client = connectContainer();
111   assert(client);
112 
113   auto result = client.execute(`return {
114                         int_val: 123,
115                         str_val: "Zdravo",
116                         float_val: 3.1415,
117                         bool_val: false,
118                         dt_val: localdatetime('2021-12-13T12:34:56.100')
119                       };`);
120   assert(result, client.error);
121   foreach (r; result) {
122     assert(r.length == 1);
123     assert(r[0].type() == Type.Map);
124     auto m = to!Map(r[0]);
125     assert(m.length == 5);
126 
127     assert(m["int_val"].type() == Type.Int);
128     assert(m["str_val"].type() == Type.String);
129     assert(m["float_val"].type() == Type.Double);
130     assert(m["bool_val"].type() == Type.Bool);
131     assert(m["dt_val"].type() == Type.LocalDateTime);
132 
133     assert(to!int(m["int_val"]) == 123);
134     assert(to!string(m["str_val"]) == "Zdravo");
135     assert(to!double(m["float_val"]) == 3.1415);
136     assert(to!bool(m["bool_val"]) == false);
137     assert(to!LocalDateTime(m["dt_val"]).toString == "2021-Dec-13 12:34:56.1");
138 
139     assert(to!string(m) == "{bool_val:false, dt_val:2021-Dec-13 12:34:56.1, " ~
140                            "float_val:3.1415, int_val:123, str_val:Zdravo}", to!string(m));
141 
142     foreach (ref key, ref value; m) {
143       assert(key in m);
144       assert(m[key] == value);
145     }
146 
147     const otherMap = m;
148     assert(otherMap == m);
149 
150     assert(cast(ulong)m.ptr == m.toHash);
151   }
152 }