1 /// 2 module dpq.query; 3 4 import dpq.connection; 5 import dpq.value; 6 import dpq.exception; 7 import dpq.result; 8 9 version(unittest) 10 { 11 import std.stdio; 12 private Connection c; 13 } 14 15 /** 16 A nice wrapper around various DB querying functions, 17 to fill even everyday PostgreSQL querying with joy. 18 19 Examples: 20 ------------------- 21 Connection c; // an established connection 22 23 Query q; // Will not have the connection set (!!!) 24 q.connection = c; // We can set it manually 25 26 q = Query(c); // Or simply use the constructor 27 ------------------- 28 */ 29 struct Query 30 { 31 private string _command; 32 private Value[] _params; 33 private Connection* _connection; 34 35 /** 36 Constructs a new Query object, reusing the last opened connection. 37 Will fail if no connection has been established. 38 39 A command string msut be provided, optionally, values can be provided too, 40 but will usually be added later on. 41 42 The query internally keeps a list of params that it will be executed with. 43 44 Please note that using the Query's empty constructor will NOT set the Query's 45 connection, and the Query will therefore be quite unusable unless you set the 46 connection later. 47 48 Examples: 49 --------------- 50 auto q = Query("SELECT 1::INT"); 51 52 auto q = Query("SELECT $1::INT", 1); 53 --------------- 54 */ 55 this(string command, Value[] params = []) 56 { 57 if (_dpqLastConnection == null) 58 throw new DPQException("Query: No established connection was found and none was provided."); 59 60 _connection = _dpqLastConnection; 61 _command = command; 62 _params = params; 63 } 64 65 /** 66 Like the above constructor, except it also accepts a Connection as the first 67 param. A copy of the Connection is not made. 68 69 Examples: 70 --------------- 71 Connection conn; // an established connection 72 auto q = Query(conn, "SELECT 1::INT"); 73 74 Connection conn; // an established connection 75 auto q = Query(conn, "SELECT $1::INT", 1); 76 --------------- 77 */ 78 this(ref Connection conn, string command = "", Value[] params = []) 79 { 80 _connection = &conn; 81 _command = command; 82 _params = params; 83 } 84 85 unittest 86 { 87 writeln(" * Query"); 88 89 c = Connection("host=127.0.0.1 dbname=test user=test"); 90 91 writeln("\t * this()"); 92 Query q; 93 // Empty constructor uses init values 94 assert(q._connection == null); 95 96 writeln("\t * this(command, params[])"); 97 string cmd = "some command"; 98 q = Query(cmd); 99 assert(q._connection != null, `not null 2`); 100 assert(q._command == cmd, `cmd`); 101 assert(q._params == [], `empty arr`); 102 103 Connection c2 = Connection("host=127.0.0.1 dbname=test user=test"); 104 writeln("\t * this(Connection, command, params[])"); 105 q = Query(c2); 106 assert(q._connection == &c2); 107 108 q = Query(cmd); 109 assert(q._connection == &c2); 110 } 111 112 /** 113 A setter for the connection. 114 115 THe connection MUST be set before executing the query, but it is a lot more 116 handy to simply use the constructor that takes the Connection instead of 117 using this. 118 */ 119 @property void connection(ref Connection conn) 120 { 121 _connection = &conn; 122 } 123 124 /** 125 A getter/setter pair for the command that will be executed. 126 */ 127 @property string command() 128 { 129 return _command; 130 } 131 132 /// ditto 133 @property void command(string c) 134 { 135 _command = c; 136 } 137 138 /** 139 Add a param to the list of params that will be sent with the query. 140 It's probably a better idea to just use run function with all the values, 141 but in some cases, adding params one by one can result in a more readable code. 142 */ 143 void addParam(T)(T val) 144 { 145 _params ~= Value(val); 146 } 147 148 unittest 149 { 150 Query q; 151 assert(q._params.length == 0); 152 153 q.addParam(1); 154 155 assert(q._params.length == 1); 156 assert(q._params[0] == Value(1)); 157 } 158 159 /** 160 In reality just an alias to addParam, but can be chained to add multiple 161 params. 162 163 Examples: 164 ----------------- 165 auto q = Query("SELECT $1, $2, $3"); 166 q << "something" << 123 << 'c'; 167 ----------------- 168 */ 169 ref Query opBinary(string op, T)(T val) 170 if (op == "<<") 171 { 172 addParam(val); 173 return this; 174 } 175 176 /** 177 Sets the query's command and resets the params. Connection is not affected 178 Useful if you want to reuse the same query object. 179 */ 180 void opAssign(string str) 181 { 182 command = str; 183 _params = []; 184 } 185 186 /** 187 Runs the Query, returning a Result object. 188 Optionally accepts a list of params for the query to be ran with. The params 189 are added to the query, and if the query is re-ran for the second time, do 190 not need to be added again. 191 192 Examples: 193 ---------------- 194 Connection c; 195 auto q = Query(c); 196 q = "SELECT $1"; 197 q.run(123); 198 ---------------- 199 */ 200 Result run() 201 { 202 import std.datetime.stopwatch : StopWatch; 203 204 StopWatch sw; 205 sw.start(); 206 207 Result r = _connection.execParams(_command, _params); 208 sw.stop(); 209 210 r.time = sw.peek(); 211 return r; 212 } 213 214 /// ditto 215 Result run(T...)(T params) 216 { 217 foreach (p; params) 218 addParam(p); 219 220 return run(); 221 } 222 223 /// ditto, async 224 bool runAsync(T...)(T params) 225 { 226 foreach (p; params) 227 addParam(p); 228 229 return runAsync(); 230 } 231 232 // ditto 233 bool runAsync() 234 { 235 _connection.sendParams(_command, _params); 236 return true; // FIXME: must return the actual result from PQsendQueryParams 237 } 238 239 unittest 240 { 241 writeln("\t * run"); 242 243 auto c = Connection("host=127.0.0.1 dbname=test user=test"); 244 245 auto q = Query("SELECT 1::INT"); 246 247 auto r = q.run(); 248 assert(r.rows == 1); 249 assert(r.columns == 1); 250 assert(r[0][0].as!int == 1); 251 252 writeln("\t\t * async"); 253 q.runAsync(); 254 255 r = c.lastResult(); 256 assert(r.rows == 1); 257 assert(r.columns == 1); 258 assert(r[0][0].as!int == 1); 259 260 writeln("\t * run(params...)"); 261 262 q = "SELECT $1"; 263 q.run(1); 264 assert(r.rows == 1); 265 assert(r.columns == 1); 266 assert(r[0][0].as!int == 1); 267 268 writeln("\t\t * async"); 269 270 q.runAsync(1); 271 r = c.lastResult(); 272 assert(r.rows == 1); 273 assert(r.columns == 1); 274 assert(r[0][0].as!int == 1); 275 } 276 }