1 /// Provides single result row or query execution summary.
2 module memgraph.result;
3 
4 import std.string, std.conv;
5 
6 import memgraph.mgclient, memgraph.value, memgraph.map, memgraph.detail, memgraph.list;
7 
8 /// An object encapsulating a single result row or query execution summary. It's
9 /// lifetime is limited by lifetime of parent `mg_session`. Also, invoking
10 /// `mg_session_pull` ends the lifetime of previously returned `mg_result`.
11 /// Implements an `InputRange`.
12 struct Result {
13 
14   /// Returns names of columns output by the current query execution.
15   ref auto columns() {
16     if (columns_.length == 0) {
17       assert(*result_ != null);
18       auto list = mg_result_columns(*result_);
19       immutable length = mg_list_size(list);
20       columns_.length = length;
21       for (uint i = 0; i < length; ++i)
22         columns_[i] = Detail.convertString(mg_value_string(mg_list_at(list, i)));
23     }
24     return columns_;
25   }
26 
27   /// Returns query execution summary as a key/value `Map`.
28   auto summary() {
29     if (summary_.length == 0) {
30       assert(*result_ != null);
31       auto map = mg_result_summary(*result_);
32       immutable length = mg_map_size(map);
33       for (uint i = 0; i < length; i++) {
34         immutable key = Detail.convertString(mg_map_key_at(map, i));
35         summary_[key] = Value(mg_map_value_at(map, i)).toString;
36       }
37     }
38     return summary_;
39   }
40 
41   /// Pulls the next result.
42   /// Return: mg_error.MG_SUCCESS on success, an `mg_error` code on failure.
43   @nogc auto pull() {
44     assert(session_ != null);
45     assert(mg_session_status(session_) == mg_session_code.MG_SESSION_EXECUTING);
46     return mg_session_pull(session_, extraParams_);
47   }
48 
49   /// Fetches the next result after a successful pull().
50   /// Return: mg_error.MG_SUCCESS on success, an `mg_error` code on failure.
51   auto fetch() {
52     assert(session_ != null);
53     assert(mg_session_status(session_) == mg_session_code.MG_SESSION_FETCHING);
54 
55     immutable status = mg_session_fetch(session_, result_);
56     assert(*result_ != null);
57     if (status == 1) {
58       // 1 - a new result row was obtained and stored in result_
59       values_ ~= List(mg_result_row(*result_));
60       return mg_error.MG_SUCCESS;
61     } else if (status == 0) {
62       // 0 - no more results, the summary was stored in result_
63       auto sum = summary();
64       if ("has_more" in sum)
65         hasMore_ = to!bool(sum["has_more"]);
66       return mg_error.MG_SUCCESS;
67     }
68     // Anything else is an error.
69     return status;
70   }
71 
72   /// Returns `true` if there are more results to be pulled from the server, `false` otherwise.
73   // @nogc auto hasMore() const { return hasMore_; }
74 
75   /// Check if the `Result` is empty.
76   /// Return: true if empty, false if there are more rows to be fetched.
77   /// Note: part of `InputRange` interface
78   @property bool empty() {
79     do {
80       hasMore_ = false;
81       if (mg_session_status(session_) == mg_session_code.MG_SESSION_EXECUTING) {
82         if (pull() != mg_error.MG_SUCCESS) return true;
83       }
84       if (mg_session_status(session_) == mg_session_code.MG_SESSION_FETCHING) {
85         if (fetch() != mg_error.MG_SUCCESS) return true;
86       }
87     } while (hasMore_);
88     return values_.length == 0;
89   }
90 
91   /// Returns the front element of the range.
92   /// Note: part of `InputRange` interface
93   @nogc auto front() {
94     assert(values_.length > 0);
95     return values_[0];
96   }
97 
98   /// Pops the first element from the range, shortening the range by one element.
99   /// Note: part of `InputRange` interface
100   @nogc @property void popFront() {
101     values_ = values_[1..$];
102   }
103 
104   /// Returns `true` if this result set is valid, `false` otherwise.
105   @nogc auto opCast(T : bool)() const {
106     return session_ != null;
107   }
108 
109 package:
110   /// Initial construction of a `Result` from the given `mg_session` pointer.
111   /// Ranges in D first perform a copy of the range object on which they will
112   /// operate. This means that the original `Result` instance could not be
113   /// used to e.g. query the summary since it does not have the last `mg_result`.
114   /// Allocate a reference counted `mg_result` pointer to be shared with all
115   /// future range copies.
116   this(mg_session *session, mg_result **result, mg_map *extraParams) {
117     assert(session != null);
118     assert(result != null);
119     session_ = session;
120     result_ = result;
121     extraParams_ = extraParams;
122   }
123 
124 private:
125   /// Pointer to `mg_session` instance.
126   mg_session *session_;
127   /// Pointer to `mg_result` instance.
128   mg_result **result_;
129   /// Extra parameters to use during pull().
130   mg_map *extraParams_;
131   /// Temporary value store.
132   List[] values_;
133   /// Flag that shows if there are more results pending to be pulled.
134   bool hasMore_;
135   /// Memoise result column names.
136   string[] columns_;
137   /// Memoise result execution summary.
138   string[string] summary_;
139 } // struct Result
140 
141 unittest {
142   import std.range.primitives : isInputRange;
143   assert(isInputRange!Result);
144 }
145 
146 unittest {
147   import testutils;
148   import memgraph : Node, Type;
149 
150   auto client = connectContainer();
151   assert(client);
152 
153   assert(client.run("CREATE INDEX ON :Person(id);"), client.error);
154 
155   assert(client.run("MATCH (n) DETACH DELETE n;"), client.error);
156 
157   foreach (id; 0..100)
158     assert(client.run("CREATE (:Person {id: " ~ to!string(id) ~ "});"), client.error);
159 
160   auto result = client.execute("MATCH (n) RETURN n;", [ "n": "10" ]);
161   assert(result, client.error);
162 
163   auto count = 0;
164   foreach (ref r; result) {
165     assert(r[0].type == Type.Node);
166     auto n = to!Node(r[0]);
167     assert(n.properties["id"] == count);
168     count++;
169   }
170   assert(count == 100);
171 }