1 /// Provides helper functions.
2 module memgraph.detail;
3 
4 import memgraph.mgclient, memgraph.enums;
5 
6 /// Wrapper class around static helper functions.
7 struct Detail {
8   /// Converts a `mg_string` to a D string.
9   @nogc static string convertString(const mg_string *str) {
10     assert(str != null);
11     const auto data = mg_string_data(str);
12     const auto len = mg_string_size(str);
13     return cast(string)data[0..len];
14   }
15 
16   unittest {
17     const p = mg_string_make("Some test string");
18     assert(p != null);
19     const s = Detail.convertString(p);
20     assert(s == "Some test string");
21   }
22 
23   /// Converts a `mg_value_type` enum to a `Type` enum.
24   @nogc static Type convertType(mg_value_type type) {
25     final switch (type) {
26       case mg_value_type.MG_VALUE_TYPE_NULL:
27         return Type.Null;
28       case mg_value_type.MG_VALUE_TYPE_BOOL:
29         return Type.Bool;
30       case mg_value_type.MG_VALUE_TYPE_INTEGER:
31         return Type.Int;
32       case mg_value_type.MG_VALUE_TYPE_FLOAT:
33         return Type.Double;
34       case mg_value_type.MG_VALUE_TYPE_STRING:
35         return Type.String;
36       case mg_value_type.MG_VALUE_TYPE_LIST:
37         return Type.List;
38       case mg_value_type.MG_VALUE_TYPE_MAP:
39         return Type.Map;
40       case mg_value_type.MG_VALUE_TYPE_NODE:
41         return Type.Node;
42       case mg_value_type.MG_VALUE_TYPE_RELATIONSHIP:
43         return Type.Relationship;
44       case mg_value_type.MG_VALUE_TYPE_UNBOUND_RELATIONSHIP:
45         return Type.UnboundRelationship;
46       case mg_value_type.MG_VALUE_TYPE_PATH:
47         return Type.Path;
48       case mg_value_type.MG_VALUE_TYPE_DATE:
49         return Type.Date;
50       case mg_value_type.MG_VALUE_TYPE_TIME:
51         assert(0);
52       case mg_value_type.MG_VALUE_TYPE_LOCAL_TIME:
53         return Type.LocalTime;
54       case mg_value_type.MG_VALUE_TYPE_DATE_TIME:
55         assert(0);
56       case mg_value_type.MG_VALUE_TYPE_DATE_TIME_ZONE_ID:
57         assert(0);
58       case mg_value_type.MG_VALUE_TYPE_LOCAL_DATE_TIME:
59         return Type.LocalDateTime;
60       case mg_value_type.MG_VALUE_TYPE_DURATION:
61         return Type.Duration;
62       case mg_value_type.MG_VALUE_TYPE_POINT_2D:
63         return Type.Point2d;
64       case mg_value_type.MG_VALUE_TYPE_POINT_3D:
65         return Type.Point3d;
66       case mg_value_type.MG_VALUE_TYPE_UNKNOWN:
67         assert(0, "unknown value type");
68     }
69   }
70 
71   unittest {
72     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_NULL) == Type.Null);
73     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_BOOL) == Type.Bool);
74     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_INTEGER) == Type.Int);
75     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_FLOAT) == Type.Double);
76     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_STRING) == Type.String);
77     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_LIST) == Type.List);
78     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_MAP) == Type.Map);
79     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_NODE) == Type.Node);
80     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_RELATIONSHIP) == Type.Relationship);
81     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_UNBOUND_RELATIONSHIP) == Type.UnboundRelationship);
82     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_PATH) == Type.Path);
83     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_DATE) == Type.Date);
84     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_LOCAL_TIME) == Type.LocalTime);
85     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_LOCAL_DATE_TIME) == Type.LocalDateTime);
86     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_DURATION) == Type.Duration);
87     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_POINT_2D) == Type.Point2d);
88     assert(Detail.convertType(mg_value_type.MG_VALUE_TYPE_POINT_3D) == Type.Point3d);
89 
90     import std.exception, core.exception;
91 
92     // The following types are currently not supported, pending addition
93     // of the appropriate temporal functions to the memgraph backend.
94     assertThrown!AssertError(Detail.convertType(mg_value_type.MG_VALUE_TYPE_TIME));
95     assertThrown!AssertError(Detail.convertType(mg_value_type.MG_VALUE_TYPE_DATE_TIME));
96     assertThrown!AssertError(Detail.convertType(mg_value_type.MG_VALUE_TYPE_DATE_TIME_ZONE_ID));
97 
98     // Check for "unknown" type.
99     assertThrown!AssertError(Detail.convertType(mg_value_type.MG_VALUE_TYPE_UNKNOWN));
100   }
101 
102   /// Compares two `mg_value`s.
103   @nogc static bool areValuesEqual(const mg_value *value1, const mg_value *value2) {
104     assert(value1 != null);
105     assert(value2 != null);
106     if (value1 == value2) {
107       return true;
108     }
109     if (mg_value_get_type(value1) != mg_value_get_type(value2)) {
110       return false;
111     }
112     final switch (mg_value_get_type(value1)) {
113       case mg_value_type.MG_VALUE_TYPE_NULL:
114         return true;
115       case mg_value_type.MG_VALUE_TYPE_BOOL:
116         return mg_value_bool(value1) == mg_value_bool(value2);
117       case mg_value_type.MG_VALUE_TYPE_INTEGER:
118         return mg_value_integer(value1) == mg_value_integer(value2);
119       case mg_value_type.MG_VALUE_TYPE_FLOAT:
120         return mg_value_float(value1) == mg_value_float(value2);
121       case mg_value_type.MG_VALUE_TYPE_STRING:
122         return Detail.convertString(mg_value_string(value1)) ==
123           Detail.convertString(mg_value_string(value2));
124       case mg_value_type.MG_VALUE_TYPE_LIST:
125         return Detail.areListsEqual(mg_value_list(value1),
126             mg_value_list(value2));
127       case mg_value_type.MG_VALUE_TYPE_MAP:
128         return Detail.areMapsEqual(mg_value_map(value1), mg_value_map(value2));
129       case mg_value_type.MG_VALUE_TYPE_NODE:
130         return Detail.areNodesEqual(mg_value_node(value1),
131             mg_value_node(value2));
132       case mg_value_type.MG_VALUE_TYPE_RELATIONSHIP:
133         return Detail.areRelationshipsEqual(mg_value_relationship(value1),
134             mg_value_relationship(value2));
135       case mg_value_type.MG_VALUE_TYPE_UNBOUND_RELATIONSHIP:
136         return Detail.areUnboundRelationshipsEqual(
137             mg_value_unbound_relationship(value1),
138             mg_value_unbound_relationship(value2));
139       case mg_value_type.MG_VALUE_TYPE_PATH:
140         return Detail.arePathsEqual(mg_value_path(value1),
141             mg_value_path(value2));
142       case mg_value_type.MG_VALUE_TYPE_DATE:
143         return Detail.areDatesEqual(mg_value_date(value1),
144             mg_value_date(value2));
145       case mg_value_type.MG_VALUE_TYPE_TIME:
146         // return Detail.areTimesEqual(mg_value_time(value1), mg_value_time(value2));
147         assert(0);
148       case mg_value_type.MG_VALUE_TYPE_LOCAL_TIME:
149         return Detail.areLocalTimesEqual(mg_value_local_time(value1),
150             mg_value_local_time(value2));
151       case mg_value_type.MG_VALUE_TYPE_DATE_TIME:
152         // return Detail.areDateTimesEqual(mg_value_date_time(value1), mg_value_date_time(value2));
153         assert(0);
154       case mg_value_type.MG_VALUE_TYPE_DATE_TIME_ZONE_ID:
155         /*
156         return Detail.areDateTimeZoneIdsEqual(
157             mg_value_date_time_zone_id(value1),
158             mg_value_date_time_zone_id(value2));
159         */
160         assert(0);
161       case mg_value_type.MG_VALUE_TYPE_LOCAL_DATE_TIME:
162         return Detail.areLocalDateTimesEqual(mg_value_local_date_time(value1),
163             mg_value_local_date_time(value2));
164       case mg_value_type.MG_VALUE_TYPE_DURATION:
165         return Detail.areDurationsEqual(mg_value_duration(value1),
166             mg_value_duration(value2));
167       case mg_value_type.MG_VALUE_TYPE_POINT_2D:
168         return Detail.arePoint2dsEqual(mg_value_point_2d(value1),
169             mg_value_point_2d(value2));
170       case mg_value_type.MG_VALUE_TYPE_POINT_3D:
171         return Detail.arePoint3dsEqual(mg_value_point_3d(value1),
172             mg_value_point_3d(value2));
173       case mg_value_type.MG_VALUE_TYPE_UNKNOWN:
174         assert(0, "comparing values of unknown types!");
175     }
176   }
177 
178   unittest {
179     auto nullValue1 = mg_value_make_null();
180     auto nullValue2 = mg_value_make_null();
181     auto stringValue1 = mg_value_make_string("Hello World");
182     auto stringValue2 = mg_value_make_string("Hello World");
183 
184     assert(Detail.areValuesEqual(nullValue1, nullValue2));
185     assert(!Detail.areValuesEqual(nullValue1, stringValue1));
186     assert(Detail.areValuesEqual(stringValue1, stringValue2));
187 
188     auto boolValue1 = mg_value_make_bool(1);
189     auto boolValue2 = mg_value_make_bool(1);
190     assert(Detail.areValuesEqual(boolValue1, boolValue2));
191 
192     auto intValue1 = mg_value_make_integer(42);
193     auto intValue2 = mg_value_make_integer(42);
194     assert(Detail.areValuesEqual(intValue1, intValue2));
195 
196     auto floatValue1 = mg_value_make_float(3.14);
197     auto floatValue2 = mg_value_make_float(3.14);
198     assert(Detail.areValuesEqual(floatValue1, floatValue2));
199   }
200 
201   /// Compares two `mg_list`s.
202   @nogc static bool areListsEqual(const mg_list *list1, const mg_list *list2) {
203     assert(list1 != null);
204     assert(list2 != null);
205     if (list1 == list2)
206       return true;
207     if (mg_list_size(list1) != mg_list_size(list2))
208       return false;
209     const uint len = mg_list_size(list1);
210     for (uint i = 0; i < len; ++i) {
211       if (!Detail.areValuesEqual(mg_list_at(list1, i), mg_list_at(list2, i)))
212         return false;
213     }
214     return true;
215   }
216 
217   unittest {
218     auto list1 = mg_list_make_empty(1);
219     mg_list_append(list1, mg_value_make_string("Value1"));
220     auto list2 = mg_list_make_empty(1);
221     mg_list_append(list2, mg_value_make_string("Value1"));
222 
223     auto listValue1 = mg_value_make_list(list1);
224     auto listValue2 = mg_value_make_list(list2);
225     assert(Detail.areValuesEqual(listValue1, listValue1));
226     assert(Detail.areValuesEqual(listValue1, listValue2));
227 
228     auto list3 = mg_list_make_empty(2);
229     mg_list_append(list3, mg_value_make_string("Value1"));
230     mg_list_append(list3, mg_value_make_string("Value2"));
231     auto listValue3 = mg_value_make_list(list3);
232     assert(!Detail.areValuesEqual(listValue1, listValue3));
233 
234     auto list4 = mg_list_make_empty(2);
235     mg_list_append(list4, mg_value_make_string("Value1"));
236     mg_list_append(list4, mg_value_make_string("Value3"));
237     auto listValue4 = mg_value_make_list(list4);
238     assert(!Detail.areValuesEqual(listValue3, listValue4));
239   }
240 
241   /// Compares two `mg_map`s.
242   @nogc static bool areMapsEqual(const mg_map *map1, const mg_map *map2) {
243     assert(map1 != null);
244     assert(map2 != null);
245     if (map1 == map2)
246       return true;
247     if (mg_map_size(map1) != mg_map_size(map2))
248       return false;
249     const uint len = mg_map_size(map1);
250     for (uint i = 0; i < len; ++i) {
251       const mg_string *key = mg_map_key_at(map1, i);
252       const mg_value *value1 = mg_map_value_at(map1, i);
253       const mg_value *value2 =
254         mg_map_at2(map2, mg_string_size(key), mg_string_data(key));
255       if (value2 == null)
256         return false;
257       if (!Detail.areValuesEqual(value1, value2))
258         return false;
259     }
260     return true;
261   }
262 
263   unittest {
264     auto map1 = mg_map_make_empty(1);
265     mg_map_insert(map1, "key1", mg_value_make_string("Value1"));
266     auto map2 = mg_map_make_empty(1);
267     mg_map_insert(map2, "key1", mg_value_make_string("Value1"));
268 
269     auto mapValue1 = mg_value_make_map(map1);
270     auto mapValue2 = mg_value_make_map(map2);
271     assert(Detail.areValuesEqual(mapValue1, mapValue2));
272     assert(Detail.areValuesEqual(mapValue1, mapValue1));
273 
274     auto map3 = mg_map_make_empty(2);
275     mg_map_insert(map3, "key1", mg_value_make_string("Value1"));
276     mg_map_insert(map3, "key2", mg_value_make_string("Value2"));
277     auto mapValue3 = mg_value_make_map(map3);
278     assert(!Detail.areValuesEqual(mapValue1, mapValue3));
279 
280     auto map4 = mg_map_make_empty(2);
281     mg_map_insert(map4, "key1", mg_value_make_string("Value1"));
282     mg_map_insert(map4, "key3", mg_value_make_string("Value3"));
283     auto mapValue4 = mg_value_make_map(map4);
284     assert(!Detail.areValuesEqual(mapValue3, mapValue4));
285 
286     auto map5 = mg_map_make_empty(2);
287     mg_map_insert(map5, "key1", mg_value_make_string("Value1"));
288     mg_map_insert(map5, "key2", mg_value_make_string("Value3"));
289     auto mapValue5 = mg_value_make_map(map5);
290     assert(!Detail.areValuesEqual(mapValue3, mapValue5));
291   }
292 
293   /// Compares two nodes for equality.
294   /// Params: node1 = first node to compare
295   ///         node2 = second node to compare
296   /// Return: `true` if both nodes are equal, `false` otherwise
297   @nogc static bool areNodesEqual(const mg_node *node1, const mg_node *node2) {
298     assert(node1 != null);
299     assert(node2 != null);
300     if (node1 == node2)
301       return true;
302     if (mg_node_id(node1) != mg_node_id(node2))
303       return false;
304     if (mg_node_label_count(node1) != mg_node_label_count(node2))
305       return false;
306     immutable label_count = mg_node_label_count(node1);
307     for (uint i = 0; i < label_count; ++i) {
308       if (Detail.convertString(mg_node_label_at(node1, i)) != Detail.convertString(mg_node_label_at(node2, i)))
309         return false;
310     }
311     return Detail.areMapsEqual(mg_node_properties(node1), mg_node_properties(node2));
312   }  // areNodesEqual()
313 
314   unittest {
315     auto label1 = mg_string_make("label1");
316     auto map1 = mg_map_make_empty(1);
317     mg_map_insert(map1, "key1", mg_value_make_string("value1"));
318     auto node1 = mg_node_make(1, 1, &label1, map1);
319     auto nodeValue1 = mg_value_make_node(node1);
320     assert(Detail.areValuesEqual(nodeValue1, nodeValue1));
321 
322     auto node2 = mg_node_make(2, 1, &label1, map1);
323     auto nodeValue2 = mg_value_make_node(node2);
324     assert(!Detail.areValuesEqual(nodeValue1, nodeValue2));
325 
326     auto label2 = mg_string_make("label2");
327     mg_string** labels = cast(mg_string**)[ label1, label2 ];
328     auto node3 = mg_node_make(1, 2, labels, map1);
329     auto nodeValue3 = mg_value_make_node(node3);
330     assert(!Detail.areValuesEqual(nodeValue1, nodeValue3));
331 
332     const label3 = mg_string_make("label3");
333     mg_string** labels2 = cast(mg_string**)[ label1, label3 ];
334     auto node4 = mg_node_make(1, 2, labels2, map1);
335     auto nodeValue4 = mg_value_make_node(node4);
336     assert(!Detail.areValuesEqual(nodeValue3, nodeValue4));
337 
338     labels2[1] = label2;
339     auto node5 = mg_node_make(1, 2, labels2, map1);
340     auto nodeValue5 = mg_value_make_node(node5);
341     assert(Detail.areValuesEqual(nodeValue3, nodeValue5));
342   }
343 
344   /// Compares two `mg_relationship`s.
345   @nogc static bool areRelationshipsEqual(const mg_relationship *rel1,
346       const mg_relationship *rel2) {
347     assert(rel1 != null);
348     assert(rel2 != null);
349     if (rel1 == rel2)
350       return true;
351     if (mg_relationship_id(rel1) != mg_relationship_id(rel2))
352       return false;
353     if (mg_relationship_start_id(rel1) != mg_relationship_start_id(rel2))
354       return false;
355     if (mg_relationship_end_id(rel1) != mg_relationship_end_id(rel2))
356       return false;
357     if (Detail.convertString(mg_relationship_type(rel1)) !=
358         Detail.convertString(mg_relationship_type(rel2)))
359       return false;
360     return Detail.areMapsEqual(mg_relationship_properties(rel1),
361         mg_relationship_properties(rel2));
362   }
363 
364   unittest {
365     auto relType1 = mg_string_make("Rel1");
366     auto props1 = mg_map_make_empty(1);
367     mg_map_insert(props1, "key1", mg_value_make_string("Value1"));
368     auto rel1 = mg_relationship_make(1, 100, 101, relType1, props1);
369     auto relValue1 = mg_value_make_relationship(rel1);
370 
371     auto relType2 = mg_string_make("Rel1");
372     auto props2 = mg_map_make_empty(1);
373     mg_map_insert(props2, "key1", mg_value_make_string("Value1"));
374     auto rel2 = mg_relationship_make(1, 100, 101, relType2, props2);
375     auto relValue2 = mg_value_make_relationship(rel2);
376 
377     assert(Detail.areValuesEqual(relValue1, relValue2));
378     rel2.id = 2;
379     assert(!Detail.areValuesEqual(relValue1, relValue2));
380     rel2.id = rel1.id;
381     rel2.start_id = 42;
382     assert(!Detail.areValuesEqual(relValue1, relValue2));
383     rel2.id = rel1.id;
384     rel2.start_id = rel1.start_id;
385     rel2.end_id = 42;
386     assert(!Detail.areValuesEqual(relValue1, relValue2));
387     auto relType3 = mg_string_make("Rel3");
388     auto rel3 = mg_relationship_make(1, 100, 101, relType3, props2);
389     auto relValue3 = mg_value_make_relationship(rel3);
390     assert(!Detail.areValuesEqual(relValue1, relValue3));
391   }
392 
393   /// Compares two `mg_unbound_relationship`s.
394   @nogc static bool areUnboundRelationshipsEqual(const mg_unbound_relationship *rel1,
395       const mg_unbound_relationship *rel2) {
396     assert(rel1 != null);
397     assert(rel2 != null);
398     if (rel1 == rel2)
399       return true;
400     if (mg_unbound_relationship_id(rel1) != mg_unbound_relationship_id(rel2))
401       return false;
402     if (Detail.convertString(mg_unbound_relationship_type(rel1)) !=
403         Detail.convertString(mg_unbound_relationship_type(rel2)))
404       return false;
405     return Detail.areMapsEqual(mg_unbound_relationship_properties(rel1),
406         mg_unbound_relationship_properties(rel2));
407   }
408 
409   unittest {
410     auto relType1 = mg_string_make("Rel1");
411     auto props1 = mg_map_make_empty(1);
412     mg_map_insert(props1, "key1", mg_value_make_string("Value1"));
413     auto rel1 = mg_unbound_relationship_make(1, relType1, props1);
414     auto relValue1 = mg_value_make_unbound_relationship(rel1);
415 
416     auto relType2 = mg_string_make("Rel1");
417     auto props2 = mg_map_make_empty(1);
418     mg_map_insert(props2, "key1", mg_value_make_string("Value1"));
419     auto rel2 = mg_unbound_relationship_make(1, relType2, props2);
420     auto relValue2 = mg_value_make_unbound_relationship(rel2);
421 
422     assert(Detail.areValuesEqual(relValue1, relValue2));
423     rel2.id = 2;
424     assert(!Detail.areValuesEqual(relValue1, relValue2));
425     auto relType3 = mg_string_make("Rel3");
426     auto rel3 = mg_unbound_relationship_make(1, relType3, props2);
427     auto relValue3 = mg_value_make_unbound_relationship(rel3);
428     assert(!Detail.areValuesEqual(relValue1, relValue3));
429   }
430 
431   /// Compares two `mg_path`s.
432   @nogc static bool arePathsEqual(const mg_path *path1, const mg_path *path2) {
433     assert(path1 != null);
434     assert(path2 != null);
435     if (path1 == path2)
436       return true;
437     if (mg_path_length(path1) != mg_path_length(path2))
438       return false;
439     const uint len = mg_path_length(path1);
440     for (uint i = 0; i < len; ++i) {
441       if (!Detail.areNodesEqual(mg_path_node_at(path1, i),
442             mg_path_node_at(path2, i))) {
443         return false;
444       }
445       if (!Detail.areUnboundRelationshipsEqual(
446             mg_path_relationship_at(path1, i),
447             mg_path_relationship_at(path2, i))) {
448         return false;
449       }
450       if (mg_path_relationship_reversed_at(path1, i) !=
451           mg_path_relationship_reversed_at(path2, i)) {
452         return false;
453       }
454     }
455     return Detail.areNodesEqual(mg_path_node_at(path1, len),
456         mg_path_node_at(path2, len));
457   }
458 
459   unittest {
460 
461     mg_node*[6] nodes;
462     nodes[0] = mg_node_make(1, 0, null, mg_map_make_empty(0));
463     nodes[1] = mg_node_make(2, 0, null, mg_map_make_empty(0));
464     nodes[2] = mg_node_make(3, 0, null, mg_map_make_empty(0));
465     nodes[3] = mg_node_make(4, 0, null, mg_map_make_empty(0));
466     nodes[4] = mg_node_make(5, 0, null, mg_map_make_empty(0));
467     nodes[5] = mg_node_make(6, 0, null, mg_map_make_empty(0));
468 
469     const auto seqs = [0L, 1L, 2L, 3L, 4L, 5L];
470 
471     mg_unbound_relationship*[6] rels;
472     rels[0] = mg_unbound_relationship_make(1, mg_string_make("Rel1"), mg_map_make_empty(0));
473     rels[1] = mg_unbound_relationship_make(2, mg_string_make("Rel1"), mg_map_make_empty(0));
474     rels[2] = mg_unbound_relationship_make(3, mg_string_make("Rel1"), mg_map_make_empty(0));
475     rels[3] = mg_unbound_relationship_make(4, mg_string_make("Rel1"), mg_map_make_empty(0));
476     rels[4] = mg_unbound_relationship_make(5, mg_string_make("Rel1"), mg_map_make_empty(0));
477     rels[5] = mg_unbound_relationship_make(6, mg_string_make("Rel2"), mg_map_make_empty(0));
478 
479     auto path1 = mg_path_make(2u, cast(mg_node**)nodes, 1u, cast(mg_unbound_relationship**)rels,
480                               2u, cast(const long*)seqs);
481     auto pathValue1 = mg_value_make_path(path1);
482     auto path2 = mg_path_make(2u, cast(mg_node**)nodes, 1u, cast(mg_unbound_relationship**)rels,
483                               2u, cast(const long*)seqs);
484     auto pathValue2 = mg_value_make_path(path2);
485 
486     assert(Detail.areValuesEqual(pathValue1, pathValue2));
487 
488     auto path3 = mg_path_make(4u, cast(mg_node**)nodes, 3u, cast(mg_unbound_relationship**)rels,
489                               4u, cast(const long*)seqs);
490     auto pathValue3 = mg_value_make_path(path3);
491 
492     assert(!Detail.areValuesEqual(pathValue1, pathValue3));
493 
494     auto path4 = mg_path_make(4u, cast(mg_node**)&nodes[1], 3u, cast(mg_unbound_relationship**)&rels[1],
495                               4u, cast(const long*)&seqs[1]);
496     auto pathValue4 = mg_value_make_path(path4);
497 
498     assert(!Detail.areValuesEqual(pathValue3, pathValue4));
499 
500     auto path5 = mg_path_make(4u, cast(mg_node**)&nodes[1], 3u, cast(mg_unbound_relationship**)&rels[2],
501                               4u, cast(const long*)&seqs[1]);
502     auto pathValue5 = mg_value_make_path(path5);
503 
504     assert(!Detail.areValuesEqual(pathValue4, pathValue5));
505 
506     // TODO: test path reversed at line 451
507   }
508 
509   /// Compares two `mg_date`s.
510   @nogc static bool areDatesEqual(const mg_date *date1, const mg_date *date2) {
511     assert(date1 != null);
512     assert(date2 != null);
513     return mg_date_days(date1) == mg_date_days(date2);
514   }
515 
516   /// Compares two `mg_time`s.
517   /*
518   @nogc static bool areTimesEqual(const mg_time *time1, const mg_time *time2) {
519     assert(time1 != null);
520     assert(time2 != null);
521     return mg_time_nanoseconds(time1) == mg_time_nanoseconds(time2) &&
522       mg_time_tz_offset_seconds(time1) == mg_time_tz_offset_seconds(time2);
523   }
524   */
525 
526   /// Compares two `mg_local_time`s.
527   @nogc static bool areLocalTimesEqual(const mg_local_time *local_time1,
528       const mg_local_time *local_time2) {
529     assert(local_time1 != null);
530     assert(local_time2 != null);
531     return mg_local_time_nanoseconds(local_time1) ==
532       mg_local_time_nanoseconds(local_time2);
533   }
534 
535   unittest {
536     auto localTime1 = mg_local_time_make(4711);
537     auto localTimeValue1 = mg_value_make_local_time(localTime1);
538     auto localTime2 = mg_local_time_make(4711);
539     auto localTimeValue2 = mg_value_make_local_time(localTime2);
540     assert(Detail.areValuesEqual(localTimeValue1, localTimeValue2));
541   }
542 
543   /// Compares two `mg_date_time`s.
544   /*
545   @nogc static bool areDateTimesEqual(const mg_date_time *date_time1,
546       const mg_date_time *date_time2) {
547     assert(date_time1 != null);
548     assert(date_time2 != null);
549     return mg_date_time_seconds(date_time1) == mg_date_time_seconds(date_time2) &&
550       mg_date_time_nanoseconds(date_time1) ==
551       mg_date_time_nanoseconds(date_time2) &&
552       mg_date_time_tz_offset_minutes(date_time1) ==
553       mg_date_time_tz_offset_minutes(date_time2);
554   }
555   */
556 
557   /// Compares two `mg_date_time_zone`s.
558   /*
559   @nogc static bool areDateTimeZoneIdsEqual(
560       const mg_date_time_zone_id *date_time_zone_id1,
561       const mg_date_time_zone_id *date_time_zone_id2) {
562     assert(date_time_zone_id1 != null);
563     assert(date_time_zone_id2 != null);
564     return mg_date_time_zone_id_seconds(date_time_zone_id1) ==
565       mg_date_time_zone_id_seconds(date_time_zone_id2) &&
566       mg_date_time_zone_id_nanoseconds(date_time_zone_id1) ==
567       mg_date_time_zone_id_nanoseconds(date_time_zone_id2) &&
568       mg_date_time_zone_id_tz_id(date_time_zone_id1) ==
569       mg_date_time_zone_id_tz_id(date_time_zone_id2);
570   }
571   */
572 
573   /// Compares two `mg_local_date_time`s.
574   @nogc static bool areLocalDateTimesEqual(const mg_local_date_time *local_date_time1,
575       const mg_local_date_time *local_date_time2) {
576     assert(local_date_time1 != null);
577     assert(local_date_time2 != null);
578     return mg_local_date_time_seconds(local_date_time1) ==
579       mg_local_date_time_seconds(local_date_time2) &&
580       mg_local_date_time_nanoseconds(local_date_time1) ==
581       mg_local_date_time_nanoseconds(local_date_time2);
582   }
583 
584   unittest {
585     auto dateTime1 = mg_local_date_time_make(100, 42);
586     auto dateTimeValue1 = mg_value_make_local_date_time(dateTime1);
587     auto dateTime2 = mg_local_date_time_make(100, 42);
588     auto dateTimeValue2 = mg_value_make_local_date_time(dateTime2);
589     assert(Detail.areValuesEqual(dateTimeValue1, dateTimeValue2));
590   }
591 
592   /// Compares two `mg_duration`s.
593   @nogc static bool areDurationsEqual(const mg_duration *duration1,
594       const mg_duration *duration2) {
595     assert(duration1 != null);
596     assert(duration2 != null);
597     return mg_duration_months(duration1) == mg_duration_months(duration2) &&
598       mg_duration_days(duration1) == mg_duration_days(duration2) &&
599       mg_duration_seconds(duration1) == mg_duration_seconds(duration2) &&
600       mg_duration_nanoseconds(duration1) ==
601       mg_duration_nanoseconds(duration2);
602   }
603 
604   unittest {
605     auto dur1 = mg_duration_make(0, 7, 100, 42);
606     auto durValue1 = mg_value_make_duration(dur1);
607     auto dur2 = mg_duration_make(0, 7, 100, 42);
608     auto durValue2 = mg_value_make_duration(dur2);
609     assert(Detail.areValuesEqual(durValue1, durValue2));
610   }
611 
612   /// Compares two `mg_point_2d`s.
613   @nogc static bool arePoint2dsEqual(const mg_point_2d *point_2d1,
614       const mg_point_2d *point_2d2) {
615     assert(point_2d1 != null);
616     assert(point_2d2 != null);
617     return mg_point_2d_srid(point_2d1) == mg_point_2d_srid(point_2d2) &&
618       mg_point_2d_x(point_2d1) == mg_point_2d_x(point_2d2) &&
619       mg_point_2d_y(point_2d1) == mg_point_2d_y(point_2d2);
620   }
621 
622   unittest {
623     auto point1 = mg_point_2d_alloc(&mg_system_allocator);
624     point1.srid = 1;
625     point1.x = 100;
626     point1.y = 100;
627     auto pointValue1 = mg_value_make_point_2d(point1);
628 
629     auto point2 = mg_point_2d_alloc(&mg_system_allocator);
630     point2.srid = 1;
631     point2.x = 100;
632     point2.y = 100;
633     auto pointValue2 = mg_value_make_point_2d(point2);
634 
635     assert(Detail.areValuesEqual(pointValue1, pointValue2));
636   }
637 
638   /// Compares two `mg_point_3d`s.
639   @nogc static bool arePoint3dsEqual(const mg_point_3d *point_3d1,
640       const mg_point_3d *point_3d2) {
641     assert(point_3d1 != null);
642     assert(point_3d2 != null);
643     return mg_point_3d_srid(point_3d1) == mg_point_3d_srid(point_3d2) &&
644       mg_point_3d_x(point_3d1) == mg_point_3d_x(point_3d2) &&
645       mg_point_3d_y(point_3d1) == mg_point_3d_y(point_3d2) &&
646       mg_point_3d_z(point_3d1) == mg_point_3d_z(point_3d2);
647   }
648 
649   unittest {
650     auto point1 = mg_point_3d_alloc(&mg_system_allocator);
651     point1.srid = 1;
652     point1.x = 100;
653     point1.y = 100;
654     point1.z = 100;
655     auto pointValue1 = mg_value_make_point_3d(point1);
656 
657     auto point2 = mg_point_3d_alloc(&mg_system_allocator);
658     point2.srid = 1;
659     point2.x = 100;
660     point2.y = 100;
661     point2.z = 100;
662     auto pointValue2 = mg_value_make_point_3d(point2);
663 
664     assert(Detail.areValuesEqual(pointValue1, pointValue2));
665   }
666 }