1 /**
2  * DDBC - D DataBase Connector - abstraction layer for RDBMS access, with interface similar to JDBC. 
3  * 
4  * Source file ddbc/drivers/pgsqlddbc.d.
5  *
6  * DDBC library attempts to provide implementation independent interface to different databases.
7  * 
8  * Set of supported RDBMSs can be extended by writing Drivers for particular DBs.
9  * Currently it only includes MySQL driver.
10  * 
11  * JDBC documentation can be found here:
12  * $(LINK http://docs.oracle.com/javase/1.5.0/docs/api/java/sql/package-summary.html)$(BR)
13  *
14  * This module contains implementation of PostgreSQL Driver
15  * 
16  *
17  * You can find usage examples in unittest{} sections.
18  *
19  * Copyright: Copyright 2013
20  * License:   $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
21  * Author:   Vadim Lopatin
22  */
23 module ddbc.drivers.pgsqlddbc;
24 
25 
26 version(USE_PGSQL) {
27     pragma(msg, "DDBC will use PGSQL driver");
28 
29     import std.algorithm;
30     import std.conv;
31     import std.datetime : Date, DateTime, TimeOfDay;
32     import std.datetime.date;
33     import std.datetime.systime;
34     import std.exception;
35 
36     // For backwards compatibily
37     // 'enforceEx' will be removed with 2.089
38     static if(__VERSION__ < 2080) {
39         alias enforceHelper = enforceEx;
40     } else {
41         alias enforceHelper = enforce;
42     }
43 
44     static if(__traits(compiles, (){ import std.experimental.logger; } )) {
45         import std.experimental.logger;
46     }
47     import std.stdio;
48     import std.string;
49     import std.variant;
50     import std.array;
51     import core.sync.mutex;
52     
53     import ddbc.common;
54     import ddbc.core;
55     import derelict.pq.pq;
56     //import ddbc.drivers.pgsql;
57     import ddbc.drivers.utils;
58 
59     const int BOOLOID = 16;
60     const int BYTEAOID = 17;
61     const int CHAROID = 18;
62     const int NAMEOID = 19;
63     const int INT8OID = 20;
64     const int INT2OID = 21;
65     const int INT2VECTOROID = 22;
66     const int INT4OID = 23;
67     const int REGPROCOID = 24;
68     const int TEXTOID = 25;
69     const int OIDOID = 26;
70     const int TIDOID = 27;
71     const int XIDOID = 28;
72     const int CIDOID = 29;
73     const int OIDVECTOROID = 30;
74     const int JSONOID = 114;
75     const int JSONBOID = 3802;
76     const int XMLOID = 142;
77     const int PGNODETREEOID = 194;
78     const int POINTOID = 600;
79     const int LSEGOID = 601;
80     const int PATHOID = 602;
81     const int BOXOID = 603;
82     const int POLYGONOID = 604;
83     const int LINEOID = 628;
84     const int FLOAT4OID = 700;
85     const int FLOAT8OID = 701;
86     const int ABSTIMEOID = 702;
87     const int RELTIMEOID = 703;
88     const int TINTERVALOID = 704;
89     const int UNKNOWNOID = 705;
90     const int CIRCLEOID = 718;
91     const int CASHOID = 790;
92     const int MACADDROID = 829;
93     const int INETOID = 869;
94     const int CIDROID = 650;
95     const int INT4ARRAYOID = 1007;
96     const int TEXTARRAYOID = 1009;
97     const int FLOAT4ARRAYOID = 1021;
98     const int ACLITEMOID = 1033;
99     const int CSTRINGARRAYOID = 1263;
100     const int BPCHAROID = 1042;
101     const int VARCHAROID = 1043;
102     const int DATEOID = 1082;
103     const int TIMEOID = 1083;
104     const int TIMESTAMPOID = 1114;
105     const int TIMESTAMPTZOID = 1184;
106     const int INTERVALOID = 1186;
107     const int TIMETZOID = 1266;
108     const int BITOID = 1560;
109     const int VARBITOID = 1562;
110     const int NUMERICOID = 1700;
111     const int REFCURSOROID = 1790;
112     const int REGPROCEDUREOID = 2202;
113     const int REGOPEROID = 2203;
114     const int REGOPERATOROID = 2204;
115     const int REGCLASSOID = 2205;
116     const int REGTYPEOID = 2206;
117     const int REGTYPEARRAYOID = 2211;
118     const int UUIDOID = 2950;
119     const int TSVECTOROID = 3614;
120     const int GTSVECTOROID = 3642;
121     const int TSQUERYOID = 3615;
122     const int REGCONFIGOID = 3734;
123     const int REGDICTIONARYOID = 3769;
124     const int INT4RANGEOID = 3904;
125     const int RECORDOID = 2249;
126     const int RECORDARRAYOID = 2287;
127     const int CSTRINGOID = 2275;
128     const int ANYOID = 2276;
129     const int ANYARRAYOID = 2277;
130     const int VOIDOID = 2278;
131     const int TRIGGEROID = 2279;
132     const int EVTTRIGGEROID = 3838;
133     const int LANGUAGE_HANDLEROID = 2280;
134     const int INTERNALOID = 2281;
135     const int OPAQUEOID = 2282;
136     const int ANYELEMENTOID = 2283;
137     const int ANYNONARRAYOID = 2776;
138     const int ANYENUMOID = 3500;
139     const int FDW_HANDLEROID = 3115;
140     const int ANYRANGEOID = 3831;
141 
142     string bytesToBytea(byte[] bytes) {
143         return ubytesToBytea(cast(ubyte[])bytes);
144     }
145 
146     string ubytesToBytea(ubyte[] bytes) {
147         if (bytes is null || !bytes.length)
148             return null;
149         char[] res;
150         res.assumeSafeAppend;
151         res ~= "\\x";
152         immutable static char[16] hex_digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
153         foreach(b; bytes) {
154             res ~= hex_digits[(b >> 4) & 0x0F];
155             res ~= hex_digits[b & 0x0F];
156         }
157         return cast(string)res;
158     }
159 
160     byte[] byteaToBytes(string s) {
161         return cast(byte[])byteaToUbytes(s);
162     }
163 
164     private static int fromHexDigit(char ch, int defValue = -1) {
165         if (ch >= '0' && ch <= '9')
166             return ch - '0';
167         if (ch >= 'A' && ch <= 'F')
168             return ch - 'A' + 10;
169         if (ch >= 'a' && ch <= 'f')
170             return ch - 'a' + 10;
171         return defValue;
172     }
173     ubyte[] byteaToUbytes(string s) {
174         if (s is null || !s.length)
175             return null;
176         ubyte[] res;
177         if (s.length > 2 && s[0] == '\\' && s[1] == 'x') {
178             // hex string format
179             for (int i = 2; i + 1 < s.length; i += 2) {
180                 int d1 = fromHexDigit(s[i], 0);
181                 int d2 = fromHexDigit(s[i + 1], 0);
182                 res ~= cast(ubyte)((d1 << 4) | (d2));
183             }
184         } else {
185             // escaped string format
186             bool lastBackSlash = 0;
187             foreach(ch; s) {
188                 if (ch == '\\') {
189                     if (lastBackSlash) {
190                         res ~= '\\';
191                         lastBackSlash = false;
192                     } else {
193                         lastBackSlash = true;
194                     }
195                 } else {
196                     if (lastBackSlash) {
197                         if (ch == '0') {
198                             res ~= 0;
199                         } else if (ch == 'r') {
200                             res ~= '\r';
201                         } else if (ch == 'n') {
202                             res ~= '\n';
203                         } else if (ch == 't') {
204                             res ~= '\t';
205                         } else {
206                         }
207                     } else {
208                         res ~= cast(byte)ch;
209                     }
210                     lastBackSlash = false;
211                 }
212             }
213         }
214         return res;
215     }
216     version(unittest) {
217     	/*
218             To allow unit tests using PostgreSQL server,
219             run postgres client using admin privileges, e.g. for postgres server on localhost:
220             sudo -u postgres psql
221 
222             Then create a user and test database:
223 
224             postgres=# CREATE USER testuser WITH ENCRYPTED PASSWORD 'testpassword';
225             postgres=# CREATE DATABASE testdb OWNER testuser;
226 
227             CREATE DATABASE testdb WITH OWNER testuser ENCODING 'UTF8' LC_COLLATE = 'en_US.UTF-8' LC_CTYPE = 'en_US.UTF-8' TEMPLATE template0;
228          */
229     	/// change to false to disable tests on real PostgreSQL server
230     	immutable bool PGSQL_TESTS_ENABLED = true;
231     	/// change parameters if necessary
232     	const string PGSQL_UNITTEST_HOST = "localhost";
233     	const int    PGSQL_UNITTEST_PORT = 5432;
234     	const string PGSQL_UNITTEST_USER = "postgres"; // "testuser";
235     	const string PGSQL_UNITTEST_PASSWORD = ""; // "testpassword";
236     	const string PGSQL_UNITTEST_DB = "testdb";
237     	
238     	static if (PGSQL_TESTS_ENABLED) {
239     		/// use this data source for tests
240     		DataSource createUnitTestPGSQLDataSource() {
241                 //string url = makeDDBCUrl("postgresql", PGSQL_UNITTEST_HOST, PGSQL_UNITTEST_PORT, PGSQL_UNITTEST_DB);
242                 string url = "ddbc:postgresql://localhost:5432/testdb";
243 
244                 string[string] params;
245                 setUserAndPassword(params, PGSQL_UNITTEST_USER, PGSQL_UNITTEST_PASSWORD);
246                 return createConnectionPool(url, params);
247     		}
248     	}
249     }
250 
251 
252     class PGSQLConnection : ddbc.core.Connection {
253     private:
254     	string url;
255     	string[string] params;
256     	string dbName;
257     	string username;
258     	string password;
259     	string hostname;
260     	int port = 5432;
261     	PGconn * conn;
262     	bool closed;
263 		bool autocommit = true;
264         bool useSsl = true;
265     	Mutex mutex;
266     	
267     	
268     	PGSQLStatement [] activeStatements;
269     	
270     	void closeUnclosedStatements() {
271     		PGSQLStatement [] list = activeStatements.dup;
272     		foreach(stmt; list) {
273     			stmt.close();
274     		}
275     	}
276     	
277         void onStatementClosed(PGSQLStatement stmt) {
278             myRemove(activeStatements, stmt);
279         }
280 
281     	void checkClosed() {
282     		if (closed)
283     			throw new SQLException("Connection is already closed");
284     	}
285     	
286     public:
287     	
288     	void lock() {
289     		mutex.lock();
290     	}
291     	
292     	void unlock() {
293     		mutex.unlock();
294     	}
295     	
296     	PGconn * getConnection() { return conn; }
297     	
298     	
299     	this(string url, string[string] params) {
300     		mutex = new Mutex();
301     		this.url = url;
302     		this.params = params;
303     		//writeln("parsing url " ~ url);
304             extractParamsFromURL(url, this.params);
305     		string dbName = "";
306     		ptrdiff_t firstSlashes = std..string.indexOf(url, "//");
307     		ptrdiff_t lastSlash = std..string.lastIndexOf(url, '/');
308     		ptrdiff_t hostNameStart = firstSlashes >= 0 ? firstSlashes + 2 : 0;
309     		ptrdiff_t hostNameEnd = lastSlash >=0 && lastSlash > firstSlashes + 1 ? lastSlash : url.length;
310     		if (hostNameEnd < url.length - 1) {
311     			dbName = url[hostNameEnd + 1 .. $];
312     		}
313     		hostname = url[hostNameStart..hostNameEnd];
314     		if (hostname.length == 0)
315     			hostname = "localhost";
316     		ptrdiff_t portDelimiter = std..string.indexOf(hostname, ":");
317     		if (portDelimiter >= 0) {
318     			string portString = hostname[portDelimiter + 1 .. $];
319     			hostname = hostname[0 .. portDelimiter];
320     			if (portString.length > 0)
321     				port = to!int(portString);
322     			if (port < 1 || port > 65535)
323     				port = 5432;
324     		}
325             if ("user" in this.params)
326     		    username = this.params["user"];
327             if ("password" in this.params)
328     		    password = this.params["password"];
329             if ("ssl" in this.params)
330                 useSsl = (this.params["ssl"] == "true");
331 
332     		
333     		//writeln("host " ~ hostname ~ " : " ~ to!string(port) ~ " db=" ~ dbName ~ " user=" ~ username ~ " pass=" ~ password);
334             // TODO: support SSL param
335 
336     		const char ** keywords = [std..string.toStringz("host"), std..string.toStringz("port"), std..string.toStringz("dbname"), std..string.toStringz("user"), std..string.toStringz("password"), null].ptr;
337     		const char ** values = [std..string.toStringz(hostname), std..string.toStringz(to!string(port)), std..string.toStringz(dbName), std..string.toStringz(username), std..string.toStringz(password), null].ptr;
338     		//writeln("trying to connect");
339     		conn = PQconnectdbParams(keywords, values, 0);
340     		if(conn is null)
341     			throw new SQLException("Cannot get Postgres connection");
342     		if(PQstatus(conn) != CONNECTION_OK)
343     			throw new SQLException(copyCString(PQerrorMessage(conn)));
344     		closed = false;
345     		setAutoCommit(true);
346     		updateConnectionParams();
347     	}
348 
349     	void updateConnectionParams() {
350     		Statement stmt = createStatement();
351     		scope(exit) stmt.close();
352     		stmt.executeUpdate("SET NAMES 'utf8'");
353     	}
354 
355     	override void close() {
356     		checkClosed();
357     		
358     		lock();
359     		scope(exit) unlock();
360     		
361     		closeUnclosedStatements();
362     		
363     		PQfinish(conn);
364     		closed = true;
365     	}
366 
367     	override void commit() {
368     		checkClosed();
369     		
370     		lock();
371     		scope(exit) unlock();
372     		
373     		Statement stmt = createStatement();
374     		scope(exit) stmt.close();
375     		stmt.executeUpdate("COMMIT");
376     	}
377 
378     	override Statement createStatement() {
379     		checkClosed();
380     		
381     		lock();
382     		scope(exit) unlock();
383     		
384     		PGSQLStatement stmt = new PGSQLStatement(this);
385     		activeStatements ~= stmt;
386     		return stmt;
387     	}
388     	
389     	PreparedStatement prepareStatement(string sql) {
390     		checkClosed();
391     		
392     		lock();
393     		scope(exit) unlock();
394     		
395     		PGSQLPreparedStatement stmt = new PGSQLPreparedStatement(this, sql);
396     		activeStatements ~= stmt;
397     		return stmt;
398     	}
399     	
400     	override string getCatalog() {
401     		return dbName;
402     	}
403     	
404     	/// Sets the given catalog name in order to select a subspace of this Connection object's database in which to work.
405     	override void setCatalog(string catalog) {
406     		checkClosed();
407     		if (dbName == catalog)
408     			return;
409     		
410     		lock();
411     		scope(exit) unlock();
412 
413     		//conn.selectDB(catalog);
414     		dbName = catalog;
415     		// TODO:
416 				
417     		throw new SQLException("Not implemented");
418     	}
419     	
420     	override bool isClosed() {
421     		return closed;
422     	}
423     	
424     	override void rollback() {
425     		checkClosed();
426     		
427     		lock();
428     		scope(exit) unlock();
429     		
430     		Statement stmt = createStatement();
431     		scope(exit) stmt.close();
432     		stmt.executeUpdate("ROLLBACK");
433     	}
434     	override bool getAutoCommit() {
435     		return autocommit;
436     	}
437     	override void setAutoCommit(bool autoCommit) {
438     		checkClosed();
439     		if (this.autocommit == autoCommit)
440     			return;
441     		lock();
442     		scope(exit) unlock();
443 
444             autocommit = true;
445 
446             //assert(0, "AUTOCOMMIT is no longer supported.");
447     	}
448     }
449 
450     class PGSQLStatement : Statement {
451     private:
452     	PGSQLConnection conn;
453     //	Command * cmd;
454     //	ddbc.drivers.mysql.ResultSet rs;
455     	PGSQLResultSet resultSet;
456     	
457     	bool closed;
458     	
459     public:
460     	void checkClosed() {
461     		enforceHelper!SQLException(!closed, "Statement is already closed");
462     	}
463     	
464     	void lock() {
465     		conn.lock();
466     	}
467     	
468     	void unlock() {
469     		conn.unlock();
470     	}
471     	
472     	this(PGSQLConnection conn) {
473     		this.conn = conn;
474     	}
475     	
476     	ResultSetMetaData createMetadata(PGresult * res) {
477     		int rows = PQntuples(res);
478     		int fieldCount = PQnfields(res);
479     		ColumnMetadataItem[] list = new ColumnMetadataItem[fieldCount];
480     		for(int i = 0; i < fieldCount; i++) {
481     			ColumnMetadataItem item = new ColumnMetadataItem();
482     			//item.schemaName = field.db;
483     			item.name = copyCString(PQfname(res, i));
484                 //item.tableName = copyCString(PQftable(res, i));
485     			int fmt = PQfformat(res, i);
486     			ulong t = PQftype(res, i);
487     			item.label = copyCString(PQfname(res, i));
488     			//item.precision = field.length;
489     			//item.scale = field.scale;
490     			//item.isNullable = !field.notNull;
491     			//item.isSigned = !field.unsigned;
492     			//item.type = fromPGSQLType(field.type);
493     //			// TODO: fill more params
494     			list[i] = item;
495     		}
496     		return new ResultSetMetaDataImpl(list);
497     	}
498     	ParameterMetaData createParameterMetadata(int paramCount) {
499             ParameterMetaDataItem[] res = new ParameterMetaDataItem[paramCount];
500             for(int i = 0; i < paramCount; i++) {
501     			ParameterMetaDataItem item = new ParameterMetaDataItem();
502     			item.precision = 0;
503     			item.scale = 0;
504     			item.isNullable = true;
505     			item.isSigned = true;
506     			item.type = SqlType.VARCHAR;
507     			res[i] = item;
508     		}
509     		return new ParameterMetaDataImpl(res);
510     	}
511     public:
512     	PGSQLConnection getConnection() {
513     		checkClosed();
514     		return conn;
515     	}
516 
517         private void fillData(PGresult * res, ref Variant[][] data) {
518             int rows = PQntuples(res);
519             int fieldCount = PQnfields(res);
520             int[] fmts = new int[fieldCount];
521             int[] types = new int[fieldCount];
522             for (int col = 0; col < fieldCount; col++) {
523                 fmts[col] = PQfformat(res, col);
524                 types[col] = cast(int)PQftype(res, col);
525             }
526             for (int row = 0; row < rows; row++) {
527                 Variant[] v = new Variant[fieldCount];
528                 for (int col = 0; col < fieldCount; col++) {
529                     int n = PQgetisnull(res, row, col);
530                     if (n != 0) {
531                         v[col] = null;
532                     } else {
533                         int len = PQgetlength(res, row, col);
534                         const ubyte * value = PQgetvalue(res, row, col);
535                         int t = types[col];
536                         //writeln("[" ~ to!string(row) ~ "][" ~ to!string(col) ~ "] type = " ~ to!string(t) ~ " len = " ~ to!string(len));
537                         if (fmts[col] == 0) {
538                             // text
539                             string s = copyCString(value, len);
540                             //writeln("text: " ~ s);
541                             switch(t) {
542                                 case INT4OID:
543                                     v[col] = parse!int(s);
544                                     break;
545                                 case BOOLOID:
546                                     if( s == "true" || s == "t" || s == "1" )
547                                         v[col] = true;
548                                     else if( s == "false" || s == "f" || s == "0" )
549                                         v[col] = false;
550                                     else
551                                         v[col] = parse!int(s) != 0;
552                                     break;
553                                 case CHAROID:
554                                     v[col] = cast(char)(s.length > 0 ? s[0] : 0);
555                                     break;
556                                 case INT8OID:
557                                     v[col] = parse!long(s);
558                                     break;
559                                 case INT2OID:
560                                     v[col] = parse!short(s);
561                                     break;
562                                 case FLOAT4OID:
563                                     v[col] = parse!float(s);
564                                     break;
565                                 case FLOAT8OID:
566                                     v[col] = parse!double(s);
567                                     break;
568                                 case VARCHAROID:
569                                 case TEXTOID:
570                                 case NAMEOID:
571                                     v[col] = s;
572                                     break;
573                                 case BYTEAOID:
574                                     v[col] = byteaToUbytes(s);
575                                     break;
576                                 case TIMESTAMPOID:
577                                     //writeln("TIMESTAMPOID: " ~ s);
578                                     v[col] = DateTime.fromISOExtString( s.translate( [ ' ': 'T' ] ).split( '.' ).front() );
579                                     // todo: use new function in ddbc.utils: parseDateTime(s);
580                                     break;
581                                 case TIMESTAMPTZOID:
582                                     //writeln("TIMESTAMPTZOID: " ~ s);
583                                     v[col] = SysTime.fromISOExtString( s.translate( [ ' ': 'T' ] ) );
584                                     // todo: use new function in ddbc.utils: parseSysTime(s);
585                                     break;
586                                 case TIMEOID:
587                                     v[col] = parseTimeoid(s);
588                                     break;
589                                 case DATEOID:
590                                     v[col] = parseDateoid(s);
591                                     break;
592                                 case UUIDOID:
593                                     v[col] = s;
594                                     break;
595                                 case JSONOID:
596                                     v[col] = s;
597                                     break;
598                                 case JSONBOID:
599                                     v[col] = s;
600                                     break;
601                                 default:
602                                     throw new SQLException("Unsupported column type " ~ to!string(t));
603                             }
604                         } else {
605                             // binary
606                             //writeln("binary:");
607                             byte[] b = new byte[len];
608                             for (int i=0; i<len; i++)
609                                 b[i] = value[i];
610                             v[col] = b;
611                         }
612                     }
613                 }
614                 data ~= v;
615             }
616         }
617 
618     	override ddbc.core.ResultSet executeQuery(string query) {
619     		//throw new SQLException("Not implemented");
620     		checkClosed();
621     		lock();
622     		scope(exit) unlock();
623 
624             static if(__traits(compiles, (){ import std.experimental.logger; } )) {
625                 sharedLog.trace(query);
626             }
627     		PGresult * res = PQexec(conn.getConnection(), std..string.toStringz(query));
628     		enforceHelper!SQLException(res !is null, "Failed to execute statement " ~ query);
629     		auto status = PQresultStatus(res);
630     		enforceHelper!SQLException(status == PGRES_TUPLES_OK, getError());
631     		scope(exit) PQclear(res);
632 
633     //		cmd = new Command(conn.getConnection(), query);
634     //		rs = cmd.execSQLResult();
635             auto metadata = createMetadata(res);
636             int rows = PQntuples(res);
637             int fieldCount = PQnfields(res);
638             Variant[][] data;
639             fillData(res, data);
640             resultSet = new PGSQLResultSet(this, data, metadata);
641     		return resultSet;
642     	}
643 
644     	string getError() {
645     		return copyCString(PQerrorMessage(conn.getConnection()));
646     	}
647 
648     	override int executeUpdate(string query) {
649     		Variant dummy;
650     		return executeUpdate(query, dummy);
651     	}
652 
653         void readInsertId(PGresult * res, ref Variant insertId) {
654             int rows = PQntuples(res);
655             int fieldCount = PQnfields(res);
656             //writeln("readInsertId - rows " ~ to!string(rows) ~ " " ~ to!string(fieldCount));
657             if (rows == 1 && fieldCount == 1) {
658                 int len = PQgetlength(res, 0, 0);
659                 const ubyte * value = PQgetvalue(res, 0, 0);
660                 string s = copyCString(value, len);
661                 insertId = parse!long(s);
662             }
663         }
664 
665     	override int executeUpdate(string query, out Variant insertId) {
666     		checkClosed();
667     		lock();
668     		scope(exit) unlock();
669 
670             static if(__traits(compiles, (){ import std.experimental.logger; } )) {
671                 sharedLog.trace(query);
672             }
673 
674     		PGresult * res = PQexec(conn.getConnection(), std..string.toStringz(query));
675     		enforceHelper!SQLException(res !is null, "Failed to execute statement " ~ query);
676     		auto status = PQresultStatus(res);
677     		enforceHelper!SQLException(status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK, getError());
678     		scope(exit) PQclear(res);
679     		
680     		string rowsAffected = copyCString(PQcmdTuples(res));
681 
682             readInsertId(res, insertId);
683 //    		auto lastid = PQoidValue(res);
684 //            writeln("lastId = " ~ to!string(lastid));
685             int affected = rowsAffected.length > 0 ? to!int(rowsAffected) : 0;
686 //    		insertId = Variant(cast(long)lastid);
687     		return affected;
688     	}
689 
690     	override void close() {
691     		checkClosed();
692     		lock();
693     		scope(exit) unlock();
694     		closeResultSet();
695     		closed = true;
696             conn.onStatementClosed(this);
697     	}
698 
699     	void closeResultSet() {
700     		//throw new SQLException("Not implemented");
701     //		if (cmd == null) {
702     //			return;
703     //		}
704     //		cmd.releaseStatement();
705     //		delete cmd;
706     //		cmd = null;
707     //		if (resultSet !is null) {
708     //			resultSet.onStatementClosed();
709     //			resultSet = null;
710     //		}
711     	}
712     }
713 
714     ulong preparedStatementIndex = 1;
715 
716     class PGSQLPreparedStatement : PGSQLStatement, PreparedStatement {
717     	string query;
718     	int paramCount;
719     	ResultSetMetaData metadata;
720     	ParameterMetaData paramMetadata;
721         string stmtName;
722         bool[] paramIsSet;
723         string[] paramValue;
724         //PGresult * rs;
725 
726         string convertParams(string query) {
727             string res;
728             int count = 0;
729             bool insideString = false;
730             char lastChar = 0;
731             foreach(ch; query) {
732                 if (ch == '\'') {
733                     if (insideString) {
734                         if (lastChar != '\\')
735                             insideString = false;
736                     } else {
737                         insideString = true;
738                     }
739                     res ~= ch;
740                 } else if (ch == '?') {
741                     if (!insideString) {
742                         count++;
743                         res ~= "$" ~ to!string(count);
744                     } else {
745                         res ~= ch;
746                     }
747                 } else {
748                     res ~= ch;
749                 }
750                 lastChar = ch;
751             }
752             paramCount = count;
753             return res;
754         }
755 
756     	this(PGSQLConnection conn, string query) {
757     		super(conn);
758             query = convertParams(query);
759             this.query = query;
760             paramMetadata = createParameterMetadata(paramCount);
761             stmtName = "ddbcstmt" ~ to!string(preparedStatementIndex);
762             paramIsSet = new bool[paramCount];
763             paramValue = new string[paramCount];
764 //            rs = PQprepare(conn.getConnection(),
765 //                                toStringz(stmtName),
766 //                                toStringz(query),
767 //                                paramCount,
768 //                                null);
769 //            enforceHelper!SQLException(rs !is null, "Error while preparing statement " ~ query);
770 //            auto status = PQresultStatus(rs);
771             //writeln("prepare paramCount = " ~ to!string(paramCount));
772 //            enforceHelper!SQLException(status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK, "Error while preparing statement " ~ query ~ " : " ~ getError(rs));
773 //            metadata = createMetadata(rs);
774             //scope(exit) PQclear(rs);
775         }
776         string getError(PGresult * res) {
777             return copyCString(PQresultErrorMessage(res));
778         }
779     	void checkIndex(int index) {
780     		if (index < 1 || index > paramCount)
781     			throw new SQLException("Parameter index " ~ to!string(index) ~ " is out of range");
782     	}
783         void checkParams() {
784             foreach(i, b; paramIsSet)
785                 enforceHelper!SQLException(b, "Parameter " ~ to!string(i) ~ " is not set");
786         }
787     	void setParam(int index, string value) {
788     		checkIndex(index);
789     		paramValue[index - 1] = value;
790             paramIsSet[index - 1] = true;
791     	}
792 
793         PGresult * exec() {
794             checkParams();
795             const (char) * [] values = new const(char)*[paramCount];
796             int[] lengths = new int[paramCount];
797             int[] formats = new int[paramCount];
798             for (int i=0; i<paramCount; i++) {
799                 if (paramValue[i] is null)
800                     values[i] = null;
801                 else
802                     values[i] = toStringz(paramValue[i]);
803                 lengths[i] = cast(int)paramValue[i].length;
804             }
805 //            PGresult * res = PQexecPrepared(conn.getConnection(),
806 //                                            toStringz(stmtName),
807 //                                            paramCount,
808 //                                            cast(const char * *)values.ptr,
809 //                                            cast(const int *)lengths.ptr,
810 //                                            cast(const int *)formats.ptr,
811 //                                            0);
812             PGresult * res = PQexecParams(conn.getConnection(),
813                                  cast(const char *)toStringz(query),
814                                  paramCount,
815                                  null,
816                                  cast(const (ubyte *) *)values.ptr,
817                                  cast(const int *)lengths.ptr,
818                                  cast(const int *)formats.ptr,
819                                  0);
820             enforceHelper!SQLException(res !is null, "Error while executing prepared statement " ~ query);
821             metadata = createMetadata(res);
822             return res;
823         }
824 
825     public:
826     	
827         override void close() {
828             checkClosed();
829             lock();
830             scope(exit) unlock();
831             //PQclear(rs);
832             closeResultSet();
833             closed = true;
834         }
835 
836     	/// Retrieves a ResultSetMetaData object that contains information about the columns of the ResultSet object that will be returned when this PreparedStatement object is executed.
837     	override ResultSetMetaData getMetaData() {
838     		checkClosed();
839     		lock();
840     		scope(exit) unlock();
841     		return metadata;
842     	}
843     	
844     	/// Retrieves the number, types and properties of this PreparedStatement object's parameters.
845     	override ParameterMetaData getParameterMetaData() {
846     		//throw new SQLException("Not implemented");
847     		checkClosed();
848     		lock();
849     		scope(exit) unlock();
850     		return paramMetadata;
851     	}
852     	
853     	override int executeUpdate() {
854             Variant dummy;
855             return executeUpdate(dummy);
856     	}
857     	
858     	override int executeUpdate(out Variant insertId) {
859     		checkClosed();
860     		lock();
861     		scope(exit) unlock();
862             PGresult * res = exec();
863             scope(exit) PQclear(res);
864             auto status = PQresultStatus(res);
865             enforceHelper!SQLException(status == PGRES_COMMAND_OK || status == PGRES_TUPLES_OK, getError(res));
866 
867             string rowsAffected = copyCString(PQcmdTuples(res));
868             //auto lastid = PQoidValue(res);
869             readInsertId(res, insertId);
870             //writeln("lastId = " ~ to!string(lastid));
871             int affected = rowsAffected.length > 0 ? to!int(rowsAffected) : 0;
872             //insertId = Variant(cast(long)lastid);
873             return affected;
874         }
875     	
876     	override ddbc.core.ResultSet executeQuery() {
877     		checkClosed();
878     		lock();
879     		scope(exit) unlock();
880 
881             static if(__traits(compiles, (){ import std.experimental.logger; } )) {
882                 sharedLog.trace(this.query);
883             }
884 
885             PGresult * res = exec();
886             scope(exit) PQclear(res);
887             int rows = PQntuples(res);
888             int fieldCount = PQnfields(res);
889             Variant[][] data;
890             fillData(res, data);
891             resultSet = new PGSQLResultSet(this, data, metadata);
892             return resultSet;
893         }
894     	
895     	override void clearParameters() {
896     		throw new SQLException("Not implemented");
897     //		checkClosed();
898     //		lock();
899     //		scope(exit) unlock();
900     //		for (int i = 1; i <= paramCount; i++)
901     //			setNull(i);
902     	}
903     	
904     	override void setFloat(int parameterIndex, float x) {
905             checkClosed();
906             lock();
907             scope(exit) unlock();
908             setParam(parameterIndex, to!string(x));
909         }
910     	override void setDouble(int parameterIndex, double x){
911             checkClosed();
912             lock();
913             scope(exit) unlock();
914             setParam(parameterIndex, to!string(x));
915         }
916     	override void setBoolean(int parameterIndex, bool x) {
917             checkClosed();
918             lock();
919             scope(exit) unlock();
920             setParam(parameterIndex, x ? "true" : "false");
921         }
922     	override void setLong(int parameterIndex, long x) {
923     		checkClosed();
924     		lock();
925     		scope(exit) unlock();
926             setParam(parameterIndex, to!string(x));
927     	}
928 
929         override void setUlong(int parameterIndex, ulong x) {
930             checkClosed();
931             lock();
932             scope(exit) unlock();
933             setParam(parameterIndex, to!string(x));
934         }
935 
936         override void setInt(int parameterIndex, int x) {
937             checkClosed();
938             lock();
939             scope(exit) unlock();
940             setParam(parameterIndex, to!string(x));
941         }
942 
943         override void setUint(int parameterIndex, uint x) {
944             checkClosed();
945             lock();
946             scope(exit) unlock();
947             setParam(parameterIndex, to!string(x));
948         }
949 
950         override void setShort(int parameterIndex, short x) {
951             checkClosed();
952             lock();
953             scope(exit) unlock();
954             setParam(parameterIndex, to!string(x));
955         }
956 
957         override void setUshort(int parameterIndex, ushort x) {
958             checkClosed();
959             lock();
960             scope(exit) unlock();
961             setParam(parameterIndex, to!string(x));
962         }
963   
964         override void setByte(int parameterIndex, byte x) {
965             checkClosed();
966             lock();
967             scope(exit) unlock();
968             setParam(parameterIndex, to!string(x));
969         }
970  
971         override void setUbyte(int parameterIndex, ubyte x) {
972             checkClosed();
973             lock();
974             scope(exit) unlock();
975             checkIndex(parameterIndex);
976             setParam(parameterIndex, to!string(x));
977         }
978    
979         override void setBytes(int parameterIndex, byte[] x) {
980             setString(parameterIndex, bytesToBytea(x));
981         }
982     	override void setUbytes(int parameterIndex, ubyte[] x) {
983             setString(parameterIndex, ubytesToBytea(x));
984         }
985     	override void setString(int parameterIndex, string x) {
986             checkClosed();
987             lock();
988             scope(exit) unlock();
989             setParam(parameterIndex, x);
990         }
991         override void setSysTime(int parameterIndex, SysTime x) {
992             setString(parameterIndex, x.toISOString());
993         }
994 
995     	override void setDateTime(int parameterIndex, DateTime x) {
996             setString(parameterIndex, x.toISOString());
997         }
998     	override void setDate(int parameterIndex, Date x) {
999             setString(parameterIndex, x.toISOString());
1000         }
1001     	override void setTime(int parameterIndex, TimeOfDay x) {
1002             setString(parameterIndex, x.toISOString());
1003         }
1004 
1005     	override void setVariant(int parameterIndex, Variant x) {
1006             checkClosed();
1007             lock();
1008             scope(exit) unlock();
1009             if (x.convertsTo!DateTime)
1010                 setDateTime(parameterIndex, x.get!DateTime);
1011             else if (x.convertsTo!Date)
1012                 setDate(parameterIndex, x.get!Date);
1013             else if (x.convertsTo!TimeOfDay)
1014                 setTime(parameterIndex, x.get!TimeOfDay);
1015             else if (x.convertsTo!(byte[]))
1016                 setBytes(parameterIndex, x.get!(byte[]));
1017             else if (x.convertsTo!(ubyte[]))
1018                 setUbytes(parameterIndex, x.get!(ubyte[]));
1019             else
1020                 setParam(parameterIndex, x.toString());
1021         }
1022 
1023         override void setNull(int parameterIndex) {
1024             checkClosed();
1025             lock();
1026             scope(exit) unlock();
1027             setParam(parameterIndex, null);
1028         }
1029 
1030         override void setNull(int parameterIndex, int sqlType) {
1031             checkClosed();
1032             lock();
1033             scope(exit) unlock();
1034             setParam(parameterIndex, null);
1035         }
1036 
1037         override string toString() {
1038             return this.query;
1039         }
1040     }
1041 
1042     class PGSQLResultSet : ResultSetImpl {
1043     	private PGSQLStatement stmt;
1044         private Variant[][] data;
1045     	ResultSetMetaData metadata;
1046     	private bool closed;
1047     	private int currentRowIndex;
1048     	private int rowCount;
1049     	private int[string] columnMap;
1050     	private bool lastIsNull;
1051     	private int columnCount;
1052     	
1053     	Variant getValue(int columnIndex) {
1054     		checkClosed();
1055     		enforceHelper!SQLException(columnIndex >= 1 && columnIndex <= columnCount, "Column index out of bounds: " ~ to!string(columnIndex));
1056     		enforceHelper!SQLException(currentRowIndex >= 0 && currentRowIndex < rowCount, "No current row in result set");
1057     		Variant res = data[currentRowIndex][columnIndex - 1];
1058             lastIsNull = (res == null);
1059     		return res;
1060     	}
1061     	
1062     	void checkClosed() {
1063     		if (closed)
1064     			throw new SQLException("Result set is already closed");
1065     	}
1066     	
1067     public:
1068     	
1069     	void lock() {
1070     		stmt.lock();
1071     	}
1072     	
1073     	void unlock() {
1074     		stmt.unlock();
1075     	}
1076     	
1077     	this(PGSQLStatement stmt, Variant[][] data, ResultSetMetaData metadata) {
1078     		this.stmt = stmt;
1079     		this.data = data;
1080     		this.metadata = metadata;
1081     		closed = false;
1082     		rowCount = cast(int)data.length;
1083     		currentRowIndex = -1;
1084     		columnCount = metadata.getColumnCount();
1085             for (int i=0; i<columnCount; i++) {
1086                 columnMap[metadata.getColumnName(i + 1)] = i;
1087             }
1088             //writeln("created result set: " ~ to!string(rowCount) ~ " rows, " ~ to!string(columnCount) ~ " cols");
1089         }
1090     	
1091     	void onStatementClosed() {
1092     		closed = true;
1093     	}
1094 
1095         // ResultSet interface implementation
1096     	
1097     	//Retrieves the number, types and properties of this ResultSet object's columns
1098     	override ResultSetMetaData getMetaData() {
1099     		checkClosed();
1100     		lock();
1101     		scope(exit) unlock();
1102     		return metadata;
1103     	}
1104     	
1105     	override void close() {
1106     		checkClosed();
1107     		lock();
1108     		scope(exit) unlock();
1109     		stmt.closeResultSet();
1110     		closed = true;
1111     	}
1112     	override bool first() {
1113     		checkClosed();
1114     		lock();
1115     		scope(exit) unlock();
1116     		currentRowIndex = 0;
1117     		return currentRowIndex >= 0 && currentRowIndex < rowCount;
1118     	}
1119     	override bool isFirst() {
1120     		checkClosed();
1121     		lock();
1122     		scope(exit) unlock();
1123     		return rowCount > 0 && currentRowIndex == 0;
1124     	}
1125     	override bool isLast() {
1126     		checkClosed();
1127     		lock();
1128     		scope(exit) unlock();
1129     		return rowCount > 0 && currentRowIndex == rowCount - 1;
1130     	}
1131     	override bool next() {
1132     		checkClosed();
1133     		lock();
1134     		scope(exit) unlock();
1135     		if (currentRowIndex + 1 >= rowCount)
1136     			return false;
1137     		currentRowIndex++;
1138     		return true;
1139     	}
1140     	
1141     	override int findColumn(string columnName) {
1142     		checkClosed();
1143     		lock();
1144     		scope(exit) unlock();
1145     		int * p = (columnName in columnMap);
1146     		if (!p)
1147     			throw new SQLException("Column " ~ columnName ~ " not found");
1148     		return *p + 1;
1149     	}
1150     	
1151     	override bool getBoolean(int columnIndex) {
1152     		checkClosed();
1153     		lock();
1154     		scope(exit) unlock();
1155     		Variant v = getValue(columnIndex);
1156     		if (lastIsNull)
1157     			return false;
1158     		if (v.convertsTo!(bool))
1159     			return v.get!(bool);
1160     		if (v.convertsTo!(int))
1161     			return v.get!(int) != 0;
1162     		if (v.convertsTo!(long))
1163     			return v.get!(long) != 0;
1164     		throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to boolean");
1165     	}
1166     	override ubyte getUbyte(int columnIndex) {
1167     		checkClosed();
1168     		lock();
1169     		scope(exit) unlock();
1170     		Variant v = getValue(columnIndex);
1171     		if (lastIsNull)
1172     			return 0;
1173     		if (v.convertsTo!(ubyte))
1174     			return v.get!(ubyte);
1175     		if (v.convertsTo!(long))
1176     			return to!ubyte(v.get!(long));
1177     		throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to ubyte");
1178     	}
1179     	override byte getByte(int columnIndex) {
1180     		checkClosed();
1181     		lock();
1182     		scope(exit) unlock();
1183     		Variant v = getValue(columnIndex);
1184     		if (lastIsNull)
1185     			return 0;
1186     		if (v.convertsTo!(byte))
1187     			return v.get!(byte);
1188     		if (v.convertsTo!(long))
1189     			return to!byte(v.get!(long));
1190     		throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to byte");
1191     	}
1192     	override short getShort(int columnIndex) {
1193     		checkClosed();
1194     		lock();
1195     		scope(exit) unlock();
1196     		Variant v = getValue(columnIndex);
1197     		if (lastIsNull)
1198     			return 0;
1199     		if (v.convertsTo!(short))
1200     			return v.get!(short);
1201     		if (v.convertsTo!(long))
1202     			return to!short(v.get!(long));
1203     		throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to short");
1204     	}
1205     	override ushort getUshort(int columnIndex) {
1206     		checkClosed();
1207     		lock();
1208     		scope(exit) unlock();
1209     		Variant v = getValue(columnIndex);
1210     		if (lastIsNull)
1211     			return 0;
1212     		if (v.convertsTo!(ushort))
1213     			return v.get!(ushort);
1214     		if (v.convertsTo!(long))
1215     			return to!ushort(v.get!(long));
1216     		throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to ushort");
1217     	}
1218     	override int getInt(int columnIndex) {
1219     		checkClosed();
1220     		lock();
1221     		scope(exit) unlock();
1222     		Variant v = getValue(columnIndex);
1223     		if (lastIsNull)
1224     			return 0;
1225     		if (v.convertsTo!(int))
1226     			return v.get!(int);
1227     		if (v.convertsTo!(long))
1228     			return to!int(v.get!(long));
1229     		throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to int");
1230     	}
1231     	override uint getUint(int columnIndex) {
1232     		checkClosed();
1233     		lock();
1234     		scope(exit) unlock();
1235     		Variant v = getValue(columnIndex);
1236     		if (lastIsNull)
1237     			return 0;
1238     		if (v.convertsTo!(uint))
1239     			return v.get!(uint);
1240     		if (v.convertsTo!(ulong))
1241     			return to!uint(v.get!(ulong));
1242     		throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to uint");
1243     	}
1244     	override long getLong(int columnIndex) {
1245     		checkClosed();
1246     		lock();
1247     		scope(exit) unlock();
1248     		Variant v = getValue(columnIndex);
1249     		if (lastIsNull)
1250     			return 0;
1251     		if (v.convertsTo!(long))
1252     			return v.get!(long);
1253     		throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to long");
1254     	}
1255     	override ulong getUlong(int columnIndex) {
1256     		checkClosed();
1257     		lock();
1258     		scope(exit) unlock();
1259     		Variant v = getValue(columnIndex);
1260     		if (lastIsNull)
1261     			return 0;
1262     		if (v.convertsTo!(ulong))
1263     			return v.get!(ulong);
1264     		throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to ulong");
1265     	}
1266     	override double getDouble(int columnIndex) {
1267     		checkClosed();
1268     		lock();
1269     		scope(exit) unlock();
1270     		Variant v = getValue(columnIndex);
1271     		if (lastIsNull)
1272     			return 0;
1273     		if (v.convertsTo!(double))
1274     			return v.get!(double);
1275     		throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to double");
1276     	}
1277     	override float getFloat(int columnIndex) {
1278     		checkClosed();
1279     		lock();
1280     		scope(exit) unlock();
1281     		Variant v = getValue(columnIndex);
1282     		if (lastIsNull)
1283     			return 0;
1284     		if (v.convertsTo!(float))
1285     			return v.get!(float);
1286     		throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to float");
1287     	}
1288     	override byte[] getBytes(int columnIndex) {
1289     		checkClosed();
1290     		lock();
1291     		scope(exit) unlock();
1292     		Variant v = getValue(columnIndex);
1293     		if (lastIsNull)
1294     			return null;
1295     		if (v.convertsTo!(byte[])) {
1296     			return v.get!(byte[]);
1297     		}
1298             return byteaToBytes(v.toString());
1299     	}
1300     	override ubyte[] getUbytes(int columnIndex) {
1301     		checkClosed();
1302     		lock();
1303     		scope(exit) unlock();
1304     		Variant v = getValue(columnIndex);
1305     		if (lastIsNull)
1306     			return null;
1307     		if (v.convertsTo!(ubyte[])) {
1308     			return v.get!(ubyte[]);
1309     		}
1310             return byteaToUbytes(v.toString());
1311         }
1312     	override string getString(int columnIndex) {
1313     		checkClosed();
1314     		lock();
1315     		scope(exit) unlock();
1316     		Variant v = getValue(columnIndex);
1317     		if (lastIsNull)
1318     			return null;
1319 //    		if (v.convertsTo!(ubyte[])) {
1320 //    			// assume blob encoding is utf-8
1321 //    			// TODO: check field encoding
1322 //    			return decodeTextBlob(v.get!(ubyte[]));
1323 //    		}
1324     		return v.toString();
1325     	}
1326 
1327         override SysTime getSysTime(int columnIndex) {
1328             checkClosed();
1329             lock();
1330             scope(exit) unlock();
1331             Variant v = getValue(columnIndex);
1332             if (lastIsNull)
1333                 return Clock.currTime();
1334             if (v.convertsTo!(SysTime)) {
1335                 return v.get!SysTime();
1336             }
1337             throw new SQLException("Cannot convert '" ~ v.toString() ~ "' to SysTime");
1338         }
1339 
1340     	override DateTime getDateTime(int columnIndex) {
1341     		checkClosed();
1342     		lock();
1343     		scope(exit) unlock();
1344     		Variant v = getValue(columnIndex);
1345     		if (lastIsNull)
1346     			return cast(DateTime) Clock.currTime();
1347     		if (v.convertsTo!(DateTime)) {
1348     			return v.get!DateTime();
1349     		}
1350     		throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to DateTime. '" ~ v.toString() ~ "'");
1351     	}
1352     	override Date getDate(int columnIndex) {
1353     		checkClosed();
1354     		lock();
1355     		scope(exit) unlock();
1356     		Variant v = getValue(columnIndex);
1357     		if (lastIsNull)
1358     			return Date();
1359     		if (v.convertsTo!(Date)) {
1360     			return v.get!Date();
1361     		}
1362     		throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to Date. '" ~ v.toString() ~ "'");
1363     	}
1364     	override TimeOfDay getTime(int columnIndex) {
1365     		checkClosed();
1366     		lock();
1367     		scope(exit) unlock();
1368     		Variant v = getValue(columnIndex);
1369     		if (lastIsNull)
1370     			return TimeOfDay();
1371     		if (v.convertsTo!(TimeOfDay)) {
1372     			return v.get!TimeOfDay();
1373     		}
1374     		throw new SQLException("Cannot convert field " ~ to!string(columnIndex) ~ " to TimeOfDay. '" ~ v.toString() ~ "'");
1375     	}
1376     	
1377     	override Variant getVariant(int columnIndex) {
1378     		checkClosed();
1379     		lock();
1380     		scope(exit) unlock();
1381     		Variant v = getValue(columnIndex);
1382     		if (lastIsNull) {
1383     			Variant vnull = null;
1384     			return vnull;
1385     		}
1386     		return v;
1387     	}
1388     	override bool wasNull() {
1389     		checkClosed();
1390     		lock();
1391     		scope(exit) unlock();
1392     		return lastIsNull;
1393     	}
1394     	override bool isNull(int columnIndex) {
1395     		checkClosed();
1396     		lock();
1397     		scope(exit) unlock();
1398     		enforceHelper!SQLException(columnIndex >= 1 && columnIndex <= columnCount, "Column index out of bounds: " ~ to!string(columnIndex));
1399     		enforceHelper!SQLException(currentRowIndex >= 0 && currentRowIndex < rowCount, "No current row in result set");
1400     		return data[currentRowIndex][columnIndex - 1] == null;
1401     	}
1402     	
1403     	//Retrieves the Statement object that produced this ResultSet object.
1404     	override Statement getStatement() {
1405     		checkClosed();
1406     		lock();
1407     		scope(exit) unlock();
1408     		return stmt;
1409     	}
1410     	
1411     	//Retrieves the current row number
1412     	override int getRow() {
1413     		checkClosed();
1414     		lock();
1415     		scope(exit) unlock();
1416     		if (currentRowIndex <0 || currentRowIndex >= rowCount)
1417     			return 0;
1418     		return currentRowIndex + 1;
1419     	}
1420     	
1421     	//Retrieves the fetch size for this ResultSet object.
1422     	override ulong getFetchSize() {
1423     		checkClosed();
1424     		lock();
1425     		scope(exit) unlock();
1426     		return rowCount;
1427     	}
1428     }
1429 
1430 
1431     // sample URL:
1432     // mysql://localhost:3306/DatabaseName
1433 
1434     //String url = "jdbc:postgresql://localhost/test";
1435     //Properties props = new Properties();
1436     //props.setProperty("user","fred");
1437     //props.setProperty("password","secret");
1438     //props.setProperty("ssl","true");
1439     //Connection conn = DriverManager.getConnection(url, props);
1440     private __gshared static bool _pqIsLoaded = false;
1441     class PGSQLDriver : Driver {
1442         this() {
1443             if (!_pqIsLoaded) {
1444                 DerelictPQ.load();
1445                 _pqIsLoaded = true;
1446             }
1447         }
1448     	// helper function
1449     	public static string generateUrl(string host = "localhost", ushort port = 5432, string dbname = null) {
1450     		return "ddbc:postgresql://" ~ host ~ ":" ~ to!string(port) ~ "/" ~ dbname;
1451     	}
1452     	public static string[string] setUserAndPassword(string username, string password) {
1453     		string[string] params;
1454     		params["user"] = username;
1455     		params["password"] = password;
1456     		params["ssl"] = "true";
1457     		return params;
1458     	}
1459     	override ddbc.core.Connection connect(string url, string[string] params) {
1460             url = stripDdbcPrefix(url);
1461     		//writeln("PGSQLDriver.connect " ~ url);
1462     		return new PGSQLConnection(url, params);
1463     	}
1464     }
1465 
1466     unittest {
1467     	static if (PGSQL_TESTS_ENABLED) {
1468     		
1469     		import std.conv;
1470     		DataSource ds = createUnitTestPGSQLDataSource();
1471     	
1472     		auto conn = ds.getConnection();
1473     		assert(conn !is null);
1474     		scope(exit) conn.close();
1475             {
1476                 //writeln("dropping table");
1477                 Statement stmt = conn.createStatement();
1478                 scope(exit) stmt.close();
1479                 stmt.executeUpdate("DROP TABLE IF EXISTS t1");
1480             }
1481             {
1482                 //writeln("creating table");
1483                 Statement stmt = conn.createStatement();
1484                 scope(exit) stmt.close();
1485                 stmt.executeUpdate("CREATE TABLE IF NOT EXISTS t1 (id SERIAL, name VARCHAR(255) NOT NULL, flags int null)");
1486                 //writeln("populating table");
1487                 Variant id = 0;
1488                 assert(stmt.executeUpdate("INSERT INTO t1 (name) VALUES ('test1') returning id", id) == 1);
1489                 assert(id.get!long > 0);
1490             }
1491             {
1492                 PreparedStatement stmt = conn.prepareStatement("INSERT INTO t1 (name) VALUES ('test2') returning id");
1493                 scope(exit) stmt.close();
1494                 Variant id = 0;
1495                 assert(stmt.executeUpdate(id) == 1);
1496                 assert(id.get!long > 0);
1497             }
1498             {
1499                 //writeln("reading table");
1500                 Statement stmt = conn.createStatement();
1501                 scope(exit) stmt.close();
1502                 ResultSet rs = stmt.executeQuery("SELECT id, name, flags FROM t1");
1503                 assert(rs.getMetaData().getColumnCount() == 3);
1504                 assert(rs.getMetaData().getColumnName(1) == "id");
1505                 assert(rs.getMetaData().getColumnName(2) == "name");
1506                 assert(rs.getMetaData().getColumnName(3) == "flags");
1507                 scope(exit) rs.close();
1508                 //writeln("id" ~ "\t" ~ "name");
1509                 while (rs.next()) {
1510                     long id = rs.getLong(1);
1511                     string name = rs.getString(2);
1512                     assert(rs.isNull(3));
1513                     //writeln("" ~ to!string(id) ~ "\t" ~ name);
1514                 }
1515             }
1516             {
1517                 //writeln("reading table");
1518                 Statement stmt = conn.createStatement();
1519                 scope(exit) stmt.close();
1520                 ResultSet rs = stmt.executeQuery("SELECT id, name, flags FROM t1");
1521                 assert(rs.getMetaData().getColumnCount() == 3);
1522                 assert(rs.getMetaData().getColumnName(1) == "id");
1523                 assert(rs.getMetaData().getColumnName(2) == "name");
1524                 assert(rs.getMetaData().getColumnName(3) == "flags");
1525                 scope(exit) rs.close();
1526                 //writeln("id" ~ "\t" ~ "name");
1527                 while (rs.next()) {
1528                     //writeln("calling getLong");
1529                     long id = rs.getLong(1);
1530                     //writeln("done getLong");
1531                     string name = rs.getString(2);
1532                     assert(rs.isNull(3));
1533                     //writeln("" ~ to!string(id) ~ "\t" ~ name);
1534                 }
1535             }
1536             {
1537                 //writeln("reading table with parameter id=1");
1538                 PreparedStatement stmt = conn.prepareStatement("SELECT id, name, flags FROM t1 WHERE id = ?");
1539                 scope(exit) stmt.close();
1540 //                assert(stmt.getMetaData().getColumnCount() == 3);
1541 //                assert(stmt.getMetaData().getColumnName(1) == "id");
1542 //                assert(stmt.getMetaData().getColumnName(2) == "name");
1543 //                assert(stmt.getMetaData().getColumnName(3) == "flags");
1544                 //writeln("calling setLong");
1545                 stmt.setLong(1, 1);
1546                 //writeln("done setLong");
1547                 {
1548                     ResultSet rs = stmt.executeQuery();
1549                     scope(exit) rs.close();
1550                     //writeln("id" ~ "\t" ~ "name");
1551                     while (rs.next()) {
1552                         long id = rs.getLong(1);
1553                         string name = rs.getString(2);
1554                         assert(rs.isNull(3));
1555                         //writeln("" ~ to!string(id) ~ "\t" ~ name);
1556                     }
1557                 }
1558                 //writeln("changing parameter id=2");
1559                 //writeln("calling setLong");
1560                 stmt.setLong(1, 2);
1561                 //writeln("done setLong");
1562                 {
1563                     ResultSet rs = stmt.executeQuery();
1564                     scope(exit) rs.close();
1565                     //writeln("id" ~ "\t" ~ "name");
1566                     while (rs.next()) {
1567                         long id = rs.getLong(1);
1568                         string name = rs.getString(2);
1569                         //writeln("" ~ to!string(id) ~ "\t" ~ name);
1570                     }
1571                 }
1572             }
1573         }
1574     }
1575 
1576     __gshared static this() {
1577         // register PGSQLDriver
1578         import ddbc.common;
1579         DriverFactory.registerDriverFactory("postgresql", delegate() { return new PGSQLDriver(); });
1580     }
1581 
1582 } else { // version(USE_PGSQL)
1583     immutable bool PGSQL_TESTS_ENABLED = false;
1584 }