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