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