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