1 module dpq.result;
2 
3 import std.conv : to;
4 import libpq.libpq;
5 
6 import std.stdio;
7 import std.string;
8 import std.typecons;
9 import std.datetime;
10 import std.traits;
11 
12 import dpq.value;
13 import dpq.exception;
14 import dpq.pgarray;
15 import dpq.smartptr;
16 
17 version(unittest)
18 {
19 	import std.stdio;
20 	import dpq.connection;
21 	Connection c;
22 }
23 
24 struct Result
25 {
26 	alias ResultPtr = SmartPointer!(PGresult*, PQclear);
27 	private ResultPtr _result;
28 	private TickDuration _time;
29 
30 	this(PGresult* res)
31 	{
32 		if (res == null)
33 		{
34 			_result = new ResultPtr(null);
35 			return;
36 		}
37 
38 		ExecStatusType status = PQresultStatus(res);
39 
40 		switch (status)
41 		{
42 			case PGRES_EMPTY_QUERY:
43 			case PGRES_BAD_RESPONSE:
44 			case PGRES_FATAL_ERROR:
45 			{
46 				string err = PQresultErrorMessage(res).fromStringz.to!string;
47 				throw new DPQException(status.to!string ~ " " ~ err);
48 			}
49 			default:
50 				break;
51 		}
52 
53 		_result = new ResultPtr(res);
54 	}
55 
56 	unittest
57 	{
58 		import std.exception;
59 
60 		writeln(" * Result");
61 		writeln("\t * this(PGresult)");
62 
63 		c = Connection("host=127.0.0.1 dbname=test user=test");
64 
65 		auto r = c.execParams("SELECT $1, $2, $3", 1, "two", 123456);
66 		assertThrown!DPQException(c.exec("SELECT_BAD_SYNTAX 1, 2, 3"));
67 	}
68 
69 	@property int rows()
70 	{
71 		int n = PQntuples(_result);
72 
73 		auto str = PQcmdTuples(_result).fromStringz;
74 		if (n == 0 && str.length > 0)
75 			return str.to!int();
76 
77 		return n;
78 	}
79 
80 	@property int cmdTuples()
81 	{
82 		auto str = PQcmdTuples(_result).fromStringz;
83 		if (str.length > 0)
84 			return str.to!int;
85 		return 0;
86 	}
87 
88 	@property int columns()
89 	{
90 		return PQnfields(_result);
91 	}
92 
93 	unittest
94 	{
95 		import dpq.attributes;
96 
97 		@relation("test_query")
98 		struct Test
99 		{
100 			@serial @PK int id;
101 			int n;
102 		}
103 		c.ensureSchema!Test;
104 
105 		foreach(i; 0 .. 100)
106 			c.insert(Test(i, i));
107 
108 		auto r = c.exec("SELECT 1 FROM test_query");
109 		writeln("\t * columns");
110 		assert(r.columns == 1);
111 
112 		writeln("\t * rows & cmdTuples");
113 		assert(r.rows == 100, `r.rows == 100`);
114 		assert(r.cmdTuples == 100, `r.cmdTuples == 0: ` ~ r.cmdTuples.to!string);
115 
116 		r = c.exec("UPDATE\"test_query\" SET n = n + 1 WHERE n < 50 RETURNING 1");
117 		assert(r.rows == 50, `r.rows == 50`);
118 		assert(r.cmdTuples == 50, `r.cmdTuples == 50`);
119 
120 		c.exec("DROP TABLE test_query");
121 	}
122 
123 	@property TickDuration time()
124 	{
125 		return _time;
126 	}
127 
128 	@property package void time(TickDuration time)
129 	{
130 		_time = time;
131 	}
132 
133 	Value get(int row, int col)
134 	{
135 		if (_result is null)
136 			throw new DPQException("Called get() on a null Result");
137 
138 		if (row >= rows())
139 			throw new DPQException("Row %d is out of range, Result has %d rows".format(row, rows()));
140 
141 		if (col >= columns())
142 			throw new DPQException("Column %d is out of range, Result has %d columns".format(col, columns()));
143 
144 		if (PQgetisnull(_result, row, col))
145 			return Value(null);
146 
147 		const(ubyte)* data = cast(ubyte *) PQgetvalue(_result, row, col);
148 		int len = PQgetlength(_result, row, col);
149 		Oid oid = PQftype(_result, col);
150 		
151 		return Value(data, len, cast(Type) oid);
152 	}
153 
154 	unittest
155 	{
156 		import std.exception;
157 
158 		writeln("\t * get");
159 		Result r;
160 		assertThrown!DPQException(r.get(0, 0));
161 
162 		int x = 123;
163 		string s = "some string";
164 
165 		r = c.execParams("SELECT $1, $2", x, s);
166 		assert(r.get(0, 0) == Value(x));
167 		assert(r.get(0, 1) == Value(s));
168 	}
169 
170 	int columnIndex(string col)
171 	{
172 		int index = PQfnumber(_result, cast(const char*)col.toStringz);
173 		if (index == -1)
174 			throw new DPQException("Column " ~ col ~ " was not found");
175 
176 		return index;
177 	}
178 
179 	string columnName(int col)
180 	{
181 		return PQfname(_result, col).fromStringz.to!string;
182 	}
183 
184 	deprecated("Use columnName instead") 
185 		alias colName = columnName;
186 
187 	unittest
188 	{
189 		writeln("\t * columnIndex");
190 		auto r = c.execParams("SELECT $1 col1, $2 col2, $3 col3", 999, 888, 777);
191 
192 		assert(r.columnIndex("col1") == 0);
193 		assert(r.columnIndex("col2") == 1);
194 		assert(r.columnIndex("col3") == 2);
195 
196 		writeln("\t * columnName");
197 
198 		assert(r.columnName(0) == "col1");
199 		assert(r.columnName(1) == "col2");
200 		assert(r.columnName(2) == "col3");
201 	}
202 
203 	int opApply(int delegate(Row) dg)
204 	{
205 		int result = 0;
206 
207 		for (int i = 0; i < this.rows; ++i)
208 		{
209 			auto row = Row(i, this);
210 			result = dg(row);
211 			if (result)
212 				break;
213 		}
214 		return result;
215 	}
216 
217 	int opApply(int delegate(int, Row) dg)
218 	{
219 		int result = 0;
220 
221 		for (int i = 0; i < this.rows; ++i)
222 		{
223 			auto row = Row(i, this);
224 			result = dg(i, row);
225 			if (result)
226 				break;
227 		}
228 		return result;
229 	}
230 
231 	Row opIndex(int row)
232 	{
233 		return Row(row, this);
234 	}
235 
236 	T opCast(T)()
237 			if (is(T == bool))
238 	{
239 		return !isNull();
240 	}
241 
242 	@property bool isNull()
243 	{
244 		return _result.isNull();
245 	}
246 }
247 
248 package struct Row
249 {
250 	private int _row;
251 	private Result* _parent;
252 	
253 	this(int row, ref Result res)
254 	{
255 		if (row >= res.rows || row < 0)
256 			throw new DPQException("Row %d out of range. Result has %d rows.".format(row, res.rows));
257 
258 		_row = row;
259 		_parent = &res;
260 	}
261 
262 	unittest
263 	{
264 		import std.exception;
265 
266 		writeln(" * Row");
267 		writeln("\t * this(row, result)");
268 		auto r = c.execParams("SELECT $1 UNION ALL SELECT $2 UNION ALL SELECT $3", 1, 2, 3);
269 		assert(r.rows == 3);
270 
271 		assertThrown!DPQException(Row(3, r));
272 		assertThrown!DPQException(Row(999, r));
273 		assertThrown!DPQException(Row(-1, r));
274 
275 		assertNotThrown!DPQException(Row(0, r));
276 		assertNotThrown!DPQException(Row(2, r));
277 	}
278 
279 	Value opIndex(int col)
280 	{
281 		return _parent.get(_row, col);
282 	}
283 
284 	Value opIndex(string col)
285 	{
286 		int c = _parent.columnIndex(col);
287 		return opIndex(c);
288 	}
289 
290 	unittest
291 	{
292 		writeln("\t * opIndex");
293 
294 		auto res = c.execParams("SELECT $1 c1, $2 c2, $3 c3", 1, 2, 3);
295 		auto r = Row(0, res);
296 
297 		assert(r[0] == Value(1));
298 		assert(r[1] == Value(2));
299 		assert(r[2] == Value(3));
300 		assert(r["c1"] == r[0]);
301 		assert(r["c2"] == r[1]);
302 		assert(r["c3"] == r[2]);
303 	}
304 
305 	int opApply(int delegate(Value) dg)
306 	{
307 		int result = 0;
308 
309 		for (int i = 0; i < _parent.columns; ++i)
310 		{
311 			auto val = this[i];
312 			result = dg(val);
313 			if (result)
314 				break;
315 		}
316 		return result;
317 	}
318 
319 	int opApply(int delegate(string, Value) dg)
320 	{
321 		int result = 0;
322 
323 		for (int i = 0; i < _parent.columns; ++i)
324 		{
325 			auto val = this[i];
326 			string name = _parent.columnName(i);
327 			result = dg(name, val);
328 			if (result)
329 				break;
330 		}
331 		return result;
332 	}
333 
334 	unittest
335 	{
336 		writeln("\t * opApply(Value)");
337 
338 		auto vs = [1, 2, 3];
339 		auto cs = ["c1", "c2", "c3"];
340 		auto r = c.execParams("SELECT $1 c1, $2 c2, $3 c3", vs[0], vs[1], vs[2]);
341 
342 		int n = 0;
343 		foreach (v; r[0])
344 			assert(v == Value(vs[n++]));
345 		assert(n == 3);
346 
347 		writeln("\t * opApply(string, Value)");
348 		n = 0;
349 		foreach (c, v; r[0])
350 		{
351 			assert(c == cs[n]);
352 			assert(v == Value(vs[n]));
353 			n += 1;
354 		}
355 	}
356 }
357 
358 
359 package T fromBytes(T)(const(ubyte)[] bytes, size_t len = 0)
360 	if (isInstanceOf!(Nullable, T))
361 {
362 	return fromBytes!(Unqual!(typeof(T.get)))(bytes, len);
363 }
364 package Nullable!T fromBytes(T)(const(ubyte)[] bytes, size_t len = 0)
365 	if (!isInstanceOf!(Nullable, T))
366 {	
367 	import std.datetime;
368 	import std.bitmanip;
369 	import std.conv : to;
370 	import std.typecons : TypedefType;
371 	import dpq.attributes;
372 
373 	alias TU = Unqual!T;
374 
375 	alias RT = Nullable!T;
376 	alias AT = TypedefType!TU;
377 
378 	static if (isSomeString!AT)
379 	{
380 		string str = cast(string)bytes[0 .. len];
381 		return Nullable!string(str);
382 	}
383 	else static if (is(AT == ubyte[]))
384 		return RT(bytes.dup);
385 	else static if (isArray!AT)
386 	{
387 		auto arr = PGArray(bytes);
388 		return RT(cast(AT)arr);
389 	}
390 	else static if (is(AT == SysTime))
391 	{
392 		SysTime t = SysTime(fromBytes!long(bytes) * 10 + SysTime(POSTGRES_EPOCH).stdTime);
393 		return RT(t);
394 	}
395 	else static if (is(AT == struct) || is(AT == class))
396 	{
397 		/*
398 			 For custom types, the data representation is the following
399 			 
400 			 First 4 bytes are an int representing the number of members
401 			 After that, the members are listed in the followign way:
402 				- OID
403 				- length
404 				- value
405 
406 			 Example: (bytes, decimal)
407 				[0 0 0 2 | 0 0 0 23 | 0 0 0 4 | 0 0 0 1 | 0 0 0 23 | 0 0 0 4 | 0 0 0 2]
408 				will represent a struct with two members, both OID 23, length 4, with values 1 and 2
409 
410 		 */
411 		alias members = serialisableMembers!T;
412 
413 		int typeLen = bytes.read!int;
414 
415 		if (typeLen == -1)
416 			return Nullable!T.init;
417 
418 		// Make sure the length matches
419 		if (typeLen != members.length)
420 			throw new DPQException("Length for %s does not match number of members".format(T.stringof));
421 
422 		// Read all the members, the order, if generated by dpq will be the same,
423 		// otherwise we can't guarantee anything and we'll still assume the
424 		// correct order.
425 		T res;
426 		foreach (mName; members)
427 		{
428 			auto member = __traits(getMember, res, mName);
429 			alias MT = TypedefType!(typeof(member));
430 
431 			Oid oid = cast(Oid) bytes.read!int;
432 			auto length = bytes.read!int;
433 
434 			// null value
435 			if (length == -1)
436 				continue;
437 
438 			static if (is(MT == class) || is(MT == struct))
439 			{
440 				__traits(getMember, res, mName) = fromBytes!MT(bytes[0 .. length]);
441 				bytes = bytes[length .. $];
442 			}
443 			else
444 			{
445 				// Confirm OIDs for scalar type
446 				if (cast(Type) oid != typeOid!MT)
447 					throw new DPQException(
448 							"Oid for `%s` in `%s` (%s) does not match the received Oid (%s)".format(
449 								mName,
450 								T.stringof,
451 								typeOid!MT.to!int,
452 								cast(Type) oid)
453 							);
454 
455 				// Make sure member's length matches
456 				if (length != MT.sizeof)
457 					throw new DPQException(
458 							"Sizeof member %s (%d) does not match length given by psql (%d)".format(
459 								mName,
460 								MT.sizeof,
461 								length)
462 							);
463 
464 				__traits(getMember, res, mName) = bytes.read!MT;
465 			}
466 		}
467 		return Nullable!T(res);
468 	}
469 	else
470 	{
471 		import std.stdio;
472 		TU res = bigEndianToNative!(AT, AT.sizeof)(bytes.to!(ubyte[AT.sizeof]));
473 		return RT(res);
474 	}
475 }
476 
477 unittest
478 {
479 	import std.bitmanip;
480 	import std.string;
481 
482 	writeln(" * fromBytes");
483 
484 	int x = 123;
485 
486 	const (ubyte)[] bs = nativeToBigEndian(x);
487 	assert(fromBytes!int(bs, x.sizeof) == x);
488 
489 	x = -555;
490 	bs = nativeToBigEndian(x);
491 	assert(fromBytes!int(bs, x.sizeof) == x);
492 
493 	x = int.min;
494 	bs = nativeToBigEndian(x);
495 	assert(fromBytes!int(bs, x.sizeof) == x);
496 
497 	x = int.max;
498 	bs = nativeToBigEndian(x);
499 	assert(fromBytes!int(bs, x.sizeof) == x);
500 
501 	string s = "some random string";
502 	assert(fromBytes!string(s.representation, s.representation.length) == s);
503 
504 	s = "";
505 	assert(fromBytes!string(s.representation, s.representation.length) == s);
506 }