1 ///
2 module dpq.result;
3 
4 import std.conv : to;
5 import libpq.libpq;
6 
7 import dpq.serialisation;
8 
9 import std.stdio;
10 import std..string;
11 import std.typecons;
12 import std.datetime;
13 import std.traits;
14 
15 import dpq.value;
16 import dpq.exception;
17 import dpq.smartptr;
18 
19 version(unittest)
20 {
21 	import std.stdio;
22 	import dpq.connection;
23 	Connection c;
24 }
25 
26 struct Result
27 {
28 	alias ResultPtr = SmartPointer!(PGresult*, PQclear);
29 	private ResultPtr _result;
30 	private Duration _time;
31 
32 	this(PGresult* res)
33 	{
34 		if (res == null)
35 		{
36 			_result = new ResultPtr(null);
37 			return;
38 		}
39 
40 		ExecStatusType status = PQresultStatus(res);
41 
42 		switch (status)
43 		{
44 			case PGRES_EMPTY_QUERY:
45 			case PGRES_BAD_RESPONSE:
46 			case PGRES_FATAL_ERROR:
47 			{
48 				string err = PQresultErrorMessage(res).fromStringz.to!string;
49 				throw new DPQException(status.to!string ~ " " ~ err);
50 			}
51 			default:
52 				break;
53 		}
54 
55 		_result = new ResultPtr(res);
56 	}
57 
58 	unittest
59 	{
60 		import std.exception;
61 
62 		writeln(" * Result");
63 		writeln("\t * this(PGresult)");
64 
65 		c = Connection("host=127.0.0.1 dbname=test user=test");
66 
67 		auto r = c.execParams("SELECT $1, $2, $3", 1, "two", 123456);
68 		assertThrown!DPQException(c.exec("SELECT_BAD_SYNTAX 1, 2, 3"));
69 	}
70 
71 	@property int rows()
72 	{
73 		int n = PQntuples(_result);
74 
75 		auto str = PQcmdTuples(_result).fromStringz;
76 		if (n == 0 && str.length > 0)
77 			return str.to!int();
78 
79 		return n;
80 	}
81 
82 	@property int cmdTuples()
83 	{
84 		auto str = PQcmdTuples(_result).fromStringz;
85 		if (str.length > 0)
86 			return str.to!int;
87 		return 0;
88 	}
89 
90 	@property int columns()
91 	{
92 		return PQnfields(_result);
93 	}
94 
95 	unittest
96 	{
97 		import dpq.attributes;
98 
99 		@relation("test_query")
100 		struct Test
101 		{
102 			@serial @PK int id;
103 			int n;
104 		}
105 		c.ensureSchema!Test;
106 
107 		foreach(i; 0 .. 100)
108 			c.insert(Test(i, i));
109 
110 		auto r = c.exec("SELECT 1 FROM test_query");
111 		writeln("\t * columns");
112 		assert(r.columns == 1);
113 
114 		writeln("\t * rows & cmdTuples");
115 		assert(r.rows == 100, `r.rows == 100`);
116 		assert(r.cmdTuples == 100, `r.cmdTuples == 0: ` ~ r.cmdTuples.to!string);
117 
118 		r = c.exec("UPDATE\"test_query\" SET n = n + 1 WHERE n < 50 RETURNING 1");
119 		assert(r.rows == 50, `r.rows == 50`);
120 		assert(r.cmdTuples == 50, `r.cmdTuples == 50`);
121 
122 		c.exec("DROP TABLE test_query");
123 	}
124 
125 	@property Duration time()
126 	{
127 		return _time;
128 	}
129 
130 	@property package void time(Duration time)
131 	{
132 		_time = time;
133 	}
134 
135 	Value get(int row, int col)
136 	{
137 		if (_result is null)
138 			throw new DPQException("Called get() on a null Result");
139 
140 		if (row >= rows())
141 			throw new DPQException("Row %d is out of range, Result has %d rows".format(row, rows()));
142 
143 		if (col >= columns())
144 			throw new DPQException("Column %d is out of range, Result has %d columns".format(col, columns()));
145 
146 		if (PQgetisnull(_result, row, col))
147 			return Value(null);
148 
149 		const(ubyte)* data = cast(ubyte *) PQgetvalue(_result, row, col);
150 		int len = PQgetlength(_result, row, col);
151 		Oid oid = PQftype(_result, col);
152 		
153 		return Value(data, len, cast(Type) oid);
154 	}
155 
156 	unittest
157 	{
158 		import std.exception;
159 
160 		writeln("\t * get");
161 		Result r;
162 		assertThrown!DPQException(r.get(0, 0));
163 
164 		int x = 123;
165 		string s = "some string";
166 
167 		r = c.execParams("SELECT $1, $2", x, s);
168 		assert(r.get(0, 0) == Value(x));
169 		assert(r.get(0, 1) == Value(s));
170 	}
171 
172 	int columnIndex(string col)
173 	{
174 		int index = PQfnumber(_result, cast(const char*)col.toStringz);
175 		if (index == -1)
176 			throw new DPQException("Column " ~ col ~ " was not found");
177 
178 		return index;
179 	}
180 
181 	string columnName(int col)
182 	{
183 		return PQfname(_result, col).fromStringz.to!string;
184 	}
185 
186 	deprecated("Use columnName instead") 
187 		alias colName = columnName;
188 
189 	unittest
190 	{
191 		writeln("\t * columnIndex");
192 		auto r = c.execParams("SELECT $1 col1, $2 col2, $3 col3", 999, 888, 777);
193 
194 		assert(r.columnIndex("col1") == 0);
195 		assert(r.columnIndex("col2") == 1);
196 		assert(r.columnIndex("col3") == 2);
197 
198 		writeln("\t * columnName");
199 
200 		assert(r.columnName(0) == "col1");
201 		assert(r.columnName(1) == "col2");
202 		assert(r.columnName(2) == "col3");
203 	}
204 
205 	/**
206 		Make result satisfy the IsInputRange constraints so we can use it
207 		with functions like map, each, ...
208 
209 		Kinda hackish for now.
210 	 */
211 	int currentRangeIndex = 0;
212 	@property bool empty()
213 	{
214 		return currentRangeIndex >= this.rows - 1;
215 	}
216 
217 	void popFront()
218 	{
219 		++currentRangeIndex;
220 	}
221 
222 	@property Row front()
223 	{
224 		return Row(currentRangeIndex, this);
225 	}
226 
227 	/**
228 		Support foreach loops, the first version with just the row, and the
229 		second also providing the index of the row.
230 
231 		Row is not sent as a reference.
232 	 */
233 	int opApply(int delegate(Row) dg)
234 	{
235 		int result = 0;
236 
237 		for (int i = 0; i < this.rows; ++i)
238 		{
239 			auto row = Row(i, this);
240 			result = dg(row);
241 			if (result)
242 				break;
243 		}
244 		return result;
245 	}
246 
247 	int opApply(int delegate(int, Row) dg)
248 	{
249 		int result = 0;
250 
251 		for (int i = 0; i < this.rows; ++i)
252 		{
253 			auto row = Row(i, this);
254 			result = dg(i, row);
255 			if (result)
256 				break;
257 		}
258 		return result;
259 	}
260 
261 	Row opIndex(int row)
262 	{
263 		return Row(row, this);
264 	}
265 
266 	T opCast(T)()
267 			if (is(T == bool))
268 	{
269 		return !isNull();
270 	}
271 
272 	@property bool isNull()
273 	{
274 		return _result.isNull();
275 	}
276 }
277 
278 //package 
279 struct Row
280 {
281 	private int _row;
282 	private Result* _parent;
283 	
284 	this(int row, ref Result res)
285 	{
286 		if (row >= res.rows || row < 0)
287 			throw new DPQException("Row %d out of range. Result has %d rows.".format(row, res.rows));
288 
289 		_row = row;
290 		_parent = &res;
291 	}
292 
293 	unittest
294 	{
295 		import std.exception;
296 
297 		writeln(" * Row");
298 		writeln("\t * this(row, result)");
299 		auto r = c.execParams("SELECT $1 UNION ALL SELECT $2 UNION ALL SELECT $3", 1, 2, 3);
300 		assert(r.rows == 3);
301 
302 		assertThrown!DPQException(Row(3, r));
303 		assertThrown!DPQException(Row(999, r));
304 		assertThrown!DPQException(Row(-1, r));
305 
306 		assertNotThrown!DPQException(Row(0, r));
307 		assertNotThrown!DPQException(Row(2, r));
308 	}
309 
310 	Value opIndex(int col)
311 	{
312 		return _parent.get(_row, col);
313 	}
314 
315 	Value opIndex(string col)
316 	{
317 		int c = _parent.columnIndex(col);
318 		return opIndex(c);
319 	}
320 
321 	unittest
322 	{
323 		writeln("\t * opIndex");
324 
325 		auto res = c.execParams("SELECT $1 c1, $2 c2, $3 c3", 1, 2, 3);
326 		auto r = Row(0, res);
327 
328 		assert(r[0] == Value(1));
329 		assert(r[1] == Value(2));
330 		assert(r[2] == Value(3));
331 		assert(r["c1"] == r[0]);
332 		assert(r["c2"] == r[1]);
333 		assert(r["c3"] == r[2]);
334 	}
335 
336 	int opApply(int delegate(Value) dg)
337 	{
338 		int result = 0;
339 
340 		for (int i = 0; i < _parent.columns; ++i)
341 		{
342 			auto val = this[i];
343 			result = dg(val);
344 			if (result)
345 				break;
346 		}
347 		return result;
348 	}
349 
350 	int opApply(int delegate(string, Value) dg)
351 	{
352 		int result = 0;
353 
354 		for (int i = 0; i < _parent.columns; ++i)
355 		{
356 			auto val = this[i];
357 			string name = _parent.columnName(i);
358 			result = dg(name, val);
359 			if (result)
360 				break;
361 		}
362 		return result;
363 	}
364 
365 	unittest
366 	{
367 		writeln("\t * opApply(Value)");
368 
369 		auto vs = [1, 2, 3];
370 		auto cs = ["c1", "c2", "c3"];
371 		auto r = c.execParams("SELECT $1 c1, $2 c2, $3 c3", vs[0], vs[1], vs[2]);
372 
373 		int n = 0;
374 		foreach (v; r[0])
375 			assert(v == Value(vs[n++]));
376 		assert(n == 3);
377 
378 		writeln("\t * opApply(string, Value)");
379 		n = 0;
380 		foreach (c, v; r[0])
381 		{
382 			assert(c == cs[n]);
383 			assert(v == Value(vs[n]));
384 			n += 1;
385 		}
386 	}
387 }
388 
389