1 /// Provides a connection for memgraph. 2 module memgraph.client; 3 4 import std.string : fromStringz, toStringz; 5 6 import memgraph.mgclient, memgraph.value, memgraph.params, memgraph.result; 7 8 /// Provides a connection for memgraph. 9 struct Client { 10 /// Disable copying. 11 @disable this(this); 12 13 /// Client software version. 14 /// Return: Client version in the major.minor.patch format. 15 @nogc static auto clientVersion() { return fromStringz(mg_client_version()); } 16 17 /// Obtains the error message stored in the current session (if any). 18 @nogc @property auto error() { 19 assert(session_ != null); 20 return fromStringz(mg_session_error(session_)); 21 } 22 23 /// Returns the status of the current session. 24 /// Return: One of the session codes in `mg_session_code`. 25 @nogc @property auto status() inout { 26 assert(session_ != null); 27 return mg_session_status(session_); 28 } 29 30 /// Runs the given Cypher `statement` and discards any possible results. 31 /// Return: true when the statement ran successfully, false otherwise. 32 bool run(const string statement) { 33 auto result = execute(statement); 34 if (!result) 35 return false; 36 foreach (r; result) {} 37 return true; 38 } 39 40 /// Executes the given Cypher `statement`. 41 /// Return: `Result` that can be used as a range e.g. using foreach() to process all results. 42 /// After executing the statement, the method is blocked until all incoming 43 /// data (execution results) are handled, i.e. until the returned `Result` has been completely processed. 44 Result execute(const string statement) { 45 string[string] emptyParams; 46 return execute(statement, emptyParams); 47 } 48 49 /// Executes the given Cypher `statement`, supplied with additional `params`. 50 /// Return: `Result` that can be used as a range e.g. using foreach() to process all results. 51 /// After executing the statement, the method is blocked until all incoming 52 /// data (execution results) are handled, i.e. until the returned `Result` has been completely processed. 53 Result execute(const string statement, const string[string] params) { 54 assert(status == mg_session_code.MG_SESSION_READY); 55 mg_error status; 56 if (params.length == 0) 57 status = mg_session_run(session_, toStringz(statement), null, null, null, null); 58 else { 59 import std.conv : to; 60 extraParams_ = mg_map_make_empty(to!uint(params.length)); 61 foreach (ref key, ref value; params) { 62 mg_error rc; 63 if (key == "n") // TODO: this needs a better solution 64 rc = mg_map_insert(extraParams_, toStringz(key), mg_value_make_integer(to!int(value))); 65 else 66 rc = mg_map_insert(extraParams_, toStringz(key), mg_value_make_string(toStringz(value))); 67 assert(rc == mg_error.MG_SUCCESS); 68 } 69 status = mg_session_run(session_, toStringz(statement), extraParams_, null, null, null); 70 } 71 if (status < 0) 72 return Result(); 73 return Result(session_, &result_, extraParams_); 74 } 75 76 /// Start a transaction. 77 /// Return: true when the transaction was successfully started, false otherwise. 78 @nogc bool begin() { 79 assert(session_ != null); 80 return mg_session_begin_transaction(session_, null) == 0; 81 } 82 83 /// Commit current transaction. 84 /// Return: true when the transaction was successfully committed, false otherwise. 85 @nogc bool commit() { 86 assert(session_ != null); 87 return mg_session_commit_transaction(session_, &result_) == 0; 88 } 89 90 /// Rollback current transaction. 91 /// Return: true when the transaction was successfully rolled back, false otherwise. 92 @nogc bool rollback() { 93 assert(session_ != null); 94 return mg_session_rollback_transaction(session_, &result_) == 0; 95 } 96 97 /// Static method that creates a Memgraph client instance using default parameters 127.0.0.1:7687 98 /// Return: client connection instance. 99 /// Returns an unconnected instance if the connection couldn't be established. 100 static Client connect() { 101 Params params; 102 return connect(params); 103 } 104 105 /// Static method that creates a Memgraph client instance. 106 /// Return: client connection instance. 107 /// If the connection couldn't be established given the `params`, it will 108 /// return an unconnected instance. 109 static Client connect(ref Params params) { 110 mg_session *session = null; 111 immutable status = mg_connect(params.ptr, &session); 112 if (status < 0) { 113 if (session) 114 mg_session_destroy(session); 115 return Client(); 116 } 117 return Client(session); 118 } 119 120 /// Destroy the internal `mg_session`. 121 @nogc ~this() { 122 if (session_) 123 mg_session_destroy(session_); 124 if (extraParams_) 125 mg_map_destroy(extraParams_); 126 } 127 128 /// Status of this client connection as boolean value. 129 /// Returns: true = the client connection was established 130 /// false = this client is not connected 131 @nogc auto opCast(T : bool)() const { return session_ != null; } 132 133 package: 134 /// Create a new instance using the given `mg_session` pointer. 135 @nogc this(mg_session *session) { 136 assert(session != null); 137 session_ = session; 138 } 139 140 auto ptr() inout { return session_; } 141 142 private: 143 mg_session *session_; 144 mg_result *result_; 145 mg_map *extraParams_; 146 } 147 148 unittest { 149 import std.exception, core.exception; 150 import testutils; 151 152 const client = connectContainer(); 153 assert(client); 154 155 assert(client.status == mg_session_code.MG_SESSION_READY); 156 assert(client.clientVersion.length > 0); 157 assert(client.ptr != null); 158 } 159 160 unittest { 161 import testutils; 162 163 auto client = connectContainer(); 164 assert(client); 165 166 assert(client.status == mg_session_code.MG_SESSION_READY); 167 assert(client.error() == "", client.error); 168 assert(client.clientVersion.length > 0); 169 } 170 171 unittest { 172 import testutils; 173 import std.algorithm : count; 174 175 auto client = connectContainer(); 176 assert(client); 177 178 createTestIndex(client); 179 180 deleteTestData(client); 181 182 // Create some test data inside a transaction, then roll it back. 183 client.begin(); 184 185 createTestData(client); 186 187 // Inside the transaction the row count should be 1. 188 auto result = client.execute("MATCH (n) RETURN n;"); 189 assert(result, client.error); 190 assert(result.count == 5); 191 192 client.rollback(); 193 194 // Outside the transaction the row count should be 0. 195 result = client.execute("MATCH (n) RETURN n;"); 196 assert(result, client.error); 197 assert(result.count == 0); 198 199 // Create some test data inside a transaction, then commit it. 200 client.begin(); 201 202 createTestData(client); 203 204 // Inside the transaction the row count should be 1. 205 result = client.execute("MATCH (n) RETURN n;"); 206 assert(result, client.error); 207 assert(result.count == 5); 208 209 client.commit(); 210 211 // Outside the transaction the row count should still be 1. 212 result = client.execute("MATCH (n) RETURN n;"); 213 assert(result, client.error); 214 assert(result.count == 5); 215 216 // Just some test for execute() using extra parameters. 217 string[string] params; 218 params["mode"] = "r"; 219 result = client.execute("MATCH (n) RETURN n;", params); 220 assert(result, client.error); 221 assert(result.count == 5); 222 223 // Just for coverage at the moment 224 assert(client.error.length >= 0); 225 assert(result.summary.length >= 0); 226 assert(result.columns == ["n"]); 227 } 228 229 unittest { 230 Params params; 231 params.host = "0.0.0.0"; 232 params.port = 12_345; 233 const client = Client.connect(params); 234 assert(!client); 235 } 236 237 unittest { 238 import testutils : connectContainer; 239 auto client = connectContainer(); 240 assert(client); 241 assert(!client.run("WHAT IS THE ANSWER TO LIFE, THE UNIVERSE AND EVERYTHING?")); 242 string[string] params; 243 params["mode"] = "r"; 244 assert(!client.execute("WHAT IS THE ANSWER TO LIFE, THE UNIVERSE AND EVERYTHING?", params)); 245 } 246 247 /// Connect example 248 unittest { 249 import std.stdio : writefln; 250 // Connect to memgraph DB at 127.0.0.1:7688 251 Params p = { host: "127.0.0.1", port: 7688 }; 252 auto client = Client.connect(p); 253 if (!client) writefln("cannot connect to %s:%s: %s", p.host, p.port, client.status); 254 } 255 256 unittest { 257 // Just for coverage. It probably will fail - unless there happens 258 // to be a memgraph server running at 127.0.0.1:7687 259 cast(void)Client.connect(); 260 }