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 }