1 /// Provides a wrapper for a Bolt value.
2 module memgraph.value;
3 
4 import std.string : toStringz;
5 import std.typecons : tuple;
6 import std.conv : to;
7 
8 import memgraph.detail;
9 import memgraph;
10 
11 // Internal mapping between D type and a tuple containing the memgraph type and the
12 // operation to be applied when doing an opCast/opEquals.
13 private static immutable enum mixinOps = [
14   typeid(double):               tuple(Type.Double,              "mg_value_float(ptr_)"),
15   typeid(int):                  tuple(Type.Int,                 "to!int(mg_value_integer(ptr_))"),
16   typeid(long):                 tuple(Type.Int,                 "to!long(mg_value_integer(ptr_))"),
17   typeid(bool):                 tuple(Type.Bool,                "to!bool(mg_value_bool(ptr_))"),
18   typeid(Node):                 tuple(Type.Node,                "Node(mg_value_node(ptr_))"),
19   typeid(List):                 tuple(Type.List,                "List(mg_value_list(ptr_))"),
20   typeid(Map):                  tuple(Type.Map,                 "Map(mg_value_map(ptr_))"),
21   typeid(Path):                 tuple(Type.Path,                "Path(mg_value_path(ptr_))"),
22   typeid(Relationship):         tuple(Type.Relationship,        "Relationship(mg_value_relationship(ptr_))"),
23   typeid(UnboundRelationship):  tuple(Type.UnboundRelationship, "UnboundRelationship(mg_value_unbound_relationship(ptr_))"),
24   typeid(string):               tuple(Type.String,              "Detail.convertString(mg_value_string(ptr_))"),
25   typeid(char[]):               tuple(Type.String,              "Detail.convertString(mg_value_string(ptr_))"),
26   typeid(Date):                 tuple(Type.Date,                "Date(mg_value_date(ptr_))"),
27   typeid(LocalTime):            tuple(Type.LocalTime,           "LocalTime(mg_value_local_time(ptr_))"),
28   typeid(LocalDateTime):        tuple(Type.LocalDateTime,       "LocalDateTime(mg_value_local_date_time(ptr_))"),
29   typeid(Duration):             tuple(Type.Duration,            "Duration(mg_value_duration(ptr_))"),
30   typeid(Point2d):              tuple(Type.Point2d,             "Point2d(mg_value_point_2d(ptr_))"),
31   typeid(Point3d):              tuple(Type.Point3d,             "Point3d(mg_value_point_3d(ptr_))"),
32 ];
33 
34 /// A Bolt value, encapsulating all other values.
35 struct Value {
36   /// Copy constructor.
37   @nogc this(const ref Value rhs) {
38     this(rhs.ptr_);
39   }
40 
41   /// Cast this value to type `T`.
42   /// Note: The code asserts that the current value holds a representation of type `T`.
43   auto opCast(T)() const {
44     assert(type == mixinOps[typeid(T)][0], "expected " ~ to!string(type) ~ " got " ~ T.stringof);
45     return mixin(mixinOps[typeid(T)][1]);
46   }
47 
48   /// Comparison operator for type `T`.
49   /// Note: The code asserts that the current value holds a representation of type `T`.
50   bool opEquals(T)(const T val) const {
51     assert(type == mixinOps[typeid(T)][0], "expected " ~ to!string(type) ~ " got " ~ T.stringof);
52     return mixin(mixinOps[typeid(T)][1]) == val;
53   }
54 
55   /// Comparison operator for another `Value`.
56   @nogc bool opEquals(const Value other) const {
57     return Detail.areValuesEqual(ptr_, other.ptr_);
58   }
59 
60   /// Return the hash code for this value.
61   size_t toHash() const nothrow @safe {
62     return cast(ulong)ptr_;
63   }
64 
65   /// Return this value as a string.
66   /// If the value held is not of type `Type.String`, then it will
67   /// be first converted into the appropriate string representation.
68   string toString() const {
69     final switch (type) {
70       case Type.Null:                return "(null)";
71       case Type.Double:              return to!string(mg_value_float(ptr_));
72       case Type.Bool:                return to!string(mg_value_bool(ptr_));
73       case Type.Int:                 return to!string(mg_value_integer(ptr_));
74       case Type.String:              return Detail.convertString(mg_value_string(ptr_));
75       case Type.Relationship:        return to!string(Relationship(mg_value_relationship(ptr_)));
76       case Type.UnboundRelationship: return to!string(UnboundRelationship(mg_value_unbound_relationship(ptr_)));
77       case Type.Node:                return to!string(Node(mg_value_node(ptr_)));
78       case Type.List:                return to!string(List(mg_value_list(ptr_)));
79       case Type.Map:                 return to!string(Map(mg_value_map(ptr_)));
80       case Type.Path:                return to!string(Path(mg_value_path(ptr_)));
81       case Type.Date:                return to!string(Date(mg_value_date(ptr_)));
82       case Type.LocalTime:           return to!string(LocalTime(mg_value_local_time(ptr_)));
83       case Type.LocalDateTime:       return to!string(LocalDateTime(mg_value_local_date_time(ptr_)));
84       case Type.Duration:            return to!string(Duration(mg_value_duration(ptr_)));
85       case Type.Point2d:             return to!string(Point2d(mg_value_point_2d(ptr_)));
86       case Type.Point3d:             return to!string(Point3d(mg_value_point_3d(ptr_)));
87     }
88   }
89 
90   /// Return the type of value being held.
91   @nogc @property Type type() const {
92     return Detail.convertType(mg_value_get_type(ptr_));
93   }
94 
95 package:
96   /// Create a Value using the given `mg_value`.
97   @nogc this(const mg_value *p) {
98     assert(p != null);
99     ptr_ = p;
100   }
101 
102   /// Returns internal `mg_value` pointer.
103   @nogc auto ptr() inout { return ptr_; }
104 
105 private:
106   const mg_value *ptr_;
107 } // struct Value
108 
109 // value tests
110 unittest {
111   import testutils : connectContainer;
112   import memgraph.local_date_time;
113   import std.conv : to;
114 
115   auto client = connectContainer();
116   assert(client);
117 
118   auto result = client.execute(`return ["Zdravo, svijete!", 42];`);
119   assert(result, client.error);
120   foreach (r; result) {
121     assert(r.length == 1);
122     assert(r[0].type() == Type.List);
123 
124     auto l = to!List(r[0]);
125 
126     assert(l.length == 2);
127 
128     assert(l[0].type() == Type.String);
129     assert(l[1].type() == Type.Int);
130 
131     assert(to!string(l[0]) == "Zdravo, svijete!");
132     assert(to!int(l[1]) == 42);
133   }
134 }
135 
136 unittest {
137   auto nullValue = Value(mg_value_make_null());
138   assert(to!string(nullValue) == "(null)");
139   assert(cast(ulong)nullValue.ptr == nullValue.toHash);
140 }