1 /// Provides a connection for memgraph. 2 module memgraph.client; 3 4 import std.string, std.stdio; 5 6 import memgraph.mgclient, memgraph.value, memgraph.map, memgraph.params, memgraph.result; 7 8 /// Provides a connection for memgraph. 9 struct Client { 10 /// Client software version. 11 /// Return: Client version in the major.minor.patch format. 12 static auto clientVersion() { return fromStringz(mg_client_version()); } 13 14 /// Obtains the error message stored in the current session (if any). 15 @property auto error() { 16 assert(ptr_ != null); 17 return fromStringz(mg_session_error(ptr_)); 18 } 19 20 /// Returns the status of the current session. 21 /// Return: One of the session codes in `mg_session_code`. 22 @property auto status() inout { 23 assert(ptr_ != null); 24 return mg_session_status(ptr_); 25 } 26 27 /// Runs the given Cypher `statement` and discards any possible results. 28 /// Return: true when the statement ran successfully, false otherwise. 29 bool run(const string statement) { 30 auto result = execute(statement); 31 if (!result) 32 return false; 33 foreach (r; result) { } 34 return true; 35 } 36 37 /// Executes the given Cypher `statement`. 38 /// Return: `Result` that can be used as a range e.g. using foreach() to process all results. 39 /// After executing the statement, the method is blocked until all incoming 40 /// data (execution results) are handled, i.e. until the returned `Result` has been completely processed. 41 Result execute(const string statement) { 42 return execute(statement, Map(0)); 43 } 44 45 /// Executes the given Cypher `statement`, supplied with additional `params`. 46 /// Return: `Result` that can be used as a range e.g. using foreach() to process all results. 47 /// After executing the statement, the method is blocked until all incoming 48 /// data (execution results) are handled, i.e. until the returned `Result` has been completely processed. 49 Result execute(const string statement, const Map params) { 50 assert(ptr_ != null); 51 int status = mg_session_run(ptr_, toStringz(statement), params.ptr, null, null, null); 52 if (status < 0) 53 return Result(); 54 status = mg_session_pull(ptr_, null); 55 if (status < 0) 56 return Result(); 57 return Result(ptr_); 58 } 59 60 /* 61 /// Fetches the next result from the input stream. 62 /// Return next result from the input stream. 63 /// If there is nothing to fetch, an empty array is returned. 64 Value[] fetchOne() { 65 // TODO: encapsulate mg_result as `Result` 66 mg_result *result; 67 Value[] values; 68 immutable status = mg_session_fetch(session, &result); 69 if (status != 1) 70 return values; 71 72 const (mg_list) *list = mg_result_row(result); 73 const size_t list_length = mg_list_size(list); 74 values.length = list_length; 75 for (uint i = 0; i < list_length; ++i) 76 values[i] = Value(mg_list_at(list, i)); 77 return values; 78 } 79 80 /// Fetches all results and discards them. 81 void discardAll() { 82 while (fetchOne()) { } 83 } 84 85 /// Fetches all results. 86 Value[][] fetchAll() { 87 Value[] maybeResult; 88 Value[][] data; 89 while ((maybeResult = fetchOne()).length > 0) 90 data ~= maybeResult; 91 return data; 92 } 93 */ 94 95 /// Start a transaction. 96 /// Return: true when the transaction was successfully started, false otherwise. 97 bool begin() { 98 assert(ptr_ != null); 99 return mg_session_begin_transaction(ptr_, null) == 0; 100 } 101 102 /// Commit current transaction. 103 /// Return: true when the transaction was successfully committed, false otherwise. 104 bool commit() { 105 assert(ptr_ != null); 106 mg_result *result; 107 return mg_session_commit_transaction(ptr_, &result) == 0; 108 } 109 110 /// Rollback current transaction. 111 /// Return: true when the transaction was successfully rolled back, false otherwise. 112 bool rollback() { 113 assert(ptr_ != null); 114 mg_result *result; 115 return mg_session_rollback_transaction(ptr_, &result) == 0; 116 } 117 118 /// Static method that creates a Memgraph client instance using default parameters localhost:7687 119 /// Return: client connection instance. 120 /// Returns an unconnected instance if the connection couldn't be established. 121 static Client connect() { 122 Params params; 123 return connect(params); 124 } 125 126 /// Static method that creates a Memgraph client instance. 127 /// Return: client connection instance. 128 /// If the connection couldn't be established given the `params`, it will 129 /// return an unconnected instance. 130 static Client connect(ref Params params) { 131 mg_session *session = null; 132 immutable status = mg_connect(params.ptr, &session); 133 if (status < 0) { 134 if (session) 135 mg_session_destroy(session); 136 return Client(); 137 } 138 return Client(session); 139 } 140 141 /// Assigns a client to another. The target of the assignment gets detached from 142 /// whatever client it was attached to, and attaches itself to the new client. 143 ref Client opAssign(ref Client rhs) @safe return { 144 import std.algorithm.mutation : swap; 145 swap(this, rhs); 146 return this; 147 } 148 149 /// Create a copy of `other` client. 150 this(ref Client other) { 151 import std.algorithm.mutation : swap; 152 swap(this, other); 153 } 154 155 @safe @nogc ~this() { 156 if (ptr_) 157 mg_session_destroy(ptr_); 158 } 159 160 @disable this(this); 161 162 auto opCast(T : bool)() const { 163 return ptr_ != null; 164 } 165 166 package: 167 this(mg_session *session) { 168 assert(session != null); 169 ptr_ = session; 170 } 171 172 private: 173 mg_session *ptr_; 174 } 175 176 unittest { 177 import std.exception, core.exception; 178 import testutils; 179 import memgraph; 180 181 auto client = connectContainer(); 182 assert(client); 183 184 assert(client.status == mg_session_code.MG_SESSION_READY); 185 assert(client.clientVersion.length > 0); 186 187 auto client2 = Client(); 188 client2 = client; 189 assert(client2.status == mg_session_code.MG_SESSION_READY); 190 assert(client2.clientVersion.length > 0); 191 192 assertThrown!AssertError(client.status); 193 assertThrown!AssertError(client.error); 194 195 auto client3 = Client(client2); 196 assert(client3.status == mg_session_code.MG_SESSION_READY); 197 assert(client3.clientVersion.length > 0); 198 199 assertThrown!AssertError(client2.status); 200 assertThrown!AssertError(client2.error); 201 } 202 203 unittest { 204 import testutils; 205 import memgraph; 206 207 auto client = connectContainer(); 208 assert(client); 209 210 assert(client.status == mg_session_code.MG_SESSION_READY); 211 212 // TODO: something weird is going on with error: 213 // with ldc2, the first character seems to be random garbage if there actually is no error 214 // and with dmd, the whole error message seems to retain it's last state, even after successful connect 215 // assert(client.error() == "", client.error); 216 217 assert(client.clientVersion.length > 0); 218 } 219 220 unittest { 221 import testutils; 222 import memgraph; 223 import std.algorithm : count; 224 225 auto client = connectContainer(); 226 assert(client); 227 228 createTestIndex(client); 229 230 deleteTestData(client); 231 232 // Create some test data inside a transaction, then roll it back. 233 client.begin(); 234 235 createTestData(client); 236 237 // Inside the transaction the row count should be 1. 238 auto result = client.execute("MATCH (n) RETURN n;"); 239 assert(result, client.error); 240 assert(result.count == 5); 241 242 client.rollback(); 243 244 // Outside the transaction the row count should be 0. 245 result = client.execute("MATCH (n) RETURN n;"); 246 assert(result, client.error); 247 assert(result.count == 0); 248 249 // Create some test data inside a transaction, then commit it. 250 client.begin(); 251 252 createTestData(client); 253 254 // Inside the transaction the row count should be 1. 255 result = client.execute("MATCH (n) RETURN n;"); 256 assert(result, client.error); 257 assert(result.count == 5); 258 259 client.commit(); 260 261 // Outside the transaction the row count should still be 1. 262 result = client.execute("MATCH (n) RETURN n;"); 263 assert(result, client.error); 264 assert(result.count == 5); 265 266 // Just some test for execute() using Map parameters. 267 auto m = Map(10); 268 m["test"] = 42; 269 result = client.execute("MATCH (n) RETURN n;", m); 270 assert(result, client.error); 271 assert(result.count == 5); 272 273 // Just for coverage at the moment 274 assert(client.error.length >= 0); 275 assert(result.summary.length >= 0); 276 assert(result.columns == ["n"]); 277 } 278 279 unittest { 280 Params params; 281 params.host = "0.0.0.0"; 282 params.port = 12_345; 283 auto client = Client.connect(params); 284 assert(!client); 285 } 286 287 unittest { 288 import testutils; 289 import memgraph; 290 auto client = connectContainer(); 291 assert(client); 292 assert(!client.run("WHAT IS THE ANSWER TO LIFE, THE UNIVERSE AND EVERYTHING?")); 293 auto m = Map(10); 294 m["answer"] = 42; 295 assert(!client.execute("WHAT IS THE ANSWER TO LIFE, THE UNIVERSE AND EVERYTHING?", m)); 296 } 297 298 /// Connect example 299 unittest { 300 import std.stdio; 301 import memgraph; 302 // Connect to memgraph DB at localhost:7688 303 Params p = { host: "localhost", port: 7688 }; 304 auto client = Client.connect(p); 305 if (!client) writefln("cannot connect to %s:%s: %s", p.host, p.port, client.status); 306 } 307 308 unittest { 309 // Just for coverage. It probably will fail - unless there happens 310 // to be a memgraph server running at localhost:7687 311 Client.connect(); 312 }