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 }