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