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