changeset 395:0eef53e06007

Add JDBC_API_Tester program to test JDBC Driver API methods and behavior of MonetDB server. It combines 30+ tests which were previous individual test programs into one large test program, reusing the connection. This speeds up testing considerably as the overhead of starting a JVM and loading the java test program class and MonetDB JDBC driver is now reduced to only one time instead of 30+ times. Also all output is no longer send to system out/err but collected in a StringBuilder. The contents of it is compared with the expected output at the end of each test. Only when it deviates the output is sent to system out, see compareExpectedOutput().
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Thu, 26 Nov 2020 18:57:28 +0100 (2020-11-26)
parents 2397e0f58949
children c763cc259132
files tests/JDBC_API_Tester.java tests/build.xml
diffstat 2 files changed, 1400 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/tests/JDBC_API_Tester.java
@@ -0,0 +1,1392 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0.  If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2020 MonetDB B.V.
+ */
+
+import java.sql.*;
+import java.util.*;
+
+/**
+ * class to test JDBC Driver API methods and behavior of MonetDB server.
+ *
+ * It combines 30+ tests which were previous individual test programs
+ * into one large test program, reusing the connection.
+ * This speeds up testing considerably as the overhead of starting a JVM and
+ * loading the java test program class and MonetDB JDBC driver is now reduced
+ * to only one time instead of 30+ times.
+ * Also all output is no longer send to system out/err but collected in a StringBuilder.
+ * The contents of it is compared with the expected output at the end of each test.
+ * Only when it deviates the output is sent to system out, see compareExpectedOutput().
+ *
+ * @author Martin van Dinther
+ * @version 0.1
+ */
+final public class JDBC_API_Tester {
+	StringBuilder sb;	// buffer to collect the test output
+	Connection con;	// main connection shared by all tests
+
+	public static void main(String[] args) throws Exception {
+		String con_URL = args[0];
+
+		JDBC_API_Tester jt = new JDBC_API_Tester();
+		jt.sb = new StringBuilder(4200);
+		jt.con = DriverManager.getConnection(con_URL);
+		// we are now connected
+
+		// run the tests
+		jt.Test_Cautocommit(con_URL);
+		jt.Test_CisValid();
+		jt.Test_Clargequery();
+		jt.Test_Cmanycon(con_URL);
+		jt.Test_Creplysize();
+		jt.Test_Csavepoints();
+		jt.Test_Ctransaction();
+		jt.Test_Dobjects();
+		jt.Test_FetchSize();
+		jt.Test_PSgeneratedkeys();
+		jt.Test_PSgetObject();
+		jt.Test_PSlargebatchval();
+		jt.Test_PSlargeresponse();
+		jt.Test_PSmanycon();
+		jt.Test_PSmetadata();
+		jt.Test_PSsomeamount();
+		jt.Test_PSsqldata();
+		jt.Test_PStimedate();
+		jt.Test_PStimezone();
+		jt.Test_PStypes();
+		jt.Test_CallableStmt();
+		jt.Test_Rbooleans();
+		jt.Test_Rmetadata();
+		jt.Test_Rpositioning();
+		jt.Test_Rsqldata();
+		jt.Test_Rtimedate();
+		jt.Test_Sbatching();
+		jt.Test_Smoreresults();
+		jt.Test_Wrapper();
+
+		jt.closeConx(jt.con);
+	}
+
+	private void Test_Cautocommit(String arg0) {
+		sb.setLength(0);	// clear the output log buffer
+
+		Connection con1 = con;
+		Connection con2 = null;
+		Statement stmt1 = null;
+		Statement stmt2 = null;
+		ResultSet rs = null;
+		try {
+			con2 = DriverManager.getConnection(arg0);
+
+			// >> true: auto commit should be on by default
+			if (con1.getAutoCommit() != true)
+				sb.append("expecting con1 to have autocommit on/true");
+			if (con2.getAutoCommit() != true)
+				sb.append("expecting con2 to have autocommit on/true");
+
+			// test commit by checking if a change is visible in another connection
+			stmt1 = con1.createStatement();
+			sb.append("1. create...");
+			stmt1.executeUpdate("CREATE TABLE table_Test_Cautocommit ( id int )");
+			sb.append("passed :)\n");
+
+			stmt2 = con2.createStatement();
+			sb.append("2. select...");
+			rs = stmt2.executeQuery("SELECT * FROM table_Test_Cautocommit");
+			sb.append("passed :)\n");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+			closeStmtResSet(stmt2, rs);
+			closeStmtResSet(stmt1, null);
+			closeConx(con2);
+			sb.append("ABORTING TEST!!!");
+			return;
+		}
+
+		try {
+			// turn off auto commit
+			con1.setAutoCommit(false);
+			con2.setAutoCommit(false);
+
+			// >> false: we just disabled it
+			if (con1.getAutoCommit() != false)
+				sb.append("expecting con1 to have autocommit off/false");
+			if (con2.getAutoCommit() != false)
+				sb.append("expecting con2 to have autocommit off/false");
+
+			// a change would not be visible now
+			sb.append("3. drop...");
+			stmt2.executeUpdate("DROP TABLE table_Test_Cautocommit");
+			sb.append("passed :)\n");
+
+			sb.append("4. select...");
+			rs = stmt1.executeQuery("SELECT * FROM table_Test_Cautocommit");
+			sb.append("passed :)\n");
+
+			sb.append("5. commit...");
+			con2.commit();
+			sb.append("passed :)\n");
+
+			sb.append("6. select...");
+			rs = stmt1.executeQuery("SELECT * FROM table_Test_Cautocommit");
+			sb.append("passed :)\n");
+
+			sb.append("7. commit...");
+			con1.commit();
+			sb.append("passed :)\n");
+
+			// restore original auto commit setting
+			con1.setAutoCommit(true);
+			con2.setAutoCommit(true);
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt1, rs);
+		closeStmtResSet(stmt2, null);
+
+		closeConx(con2);
+
+		compareExpectedOutput("Test_Cautocommit",
+				"1. create...passed :)\n" +
+				"2. select...passed :)\n" +
+				"3. drop...passed :)\n" +
+				"4. select...passed :)\n" +
+				"5. commit...passed :)\n" +
+				"6. select...passed :)\n" +
+				"7. commit...passed :)\n");
+	}
+
+	private void Test_CisValid() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+			con.setAutoCommit(false); // start a transaction
+			stmt.executeQuery("SELECT COUNT(*) FROM doesnotexist;"); // let's trigger an error
+		} catch (SQLException e) {
+			// e.printStackTrace();
+			sb.append("Expected error: " + e).append("\n");
+			try {
+				// test calling conn.isValid()
+				sb.append("Validating connection: con.isValid? " + con.isValid(30));
+				// Can we rollback on this connection without causing an error?
+				con.rollback();
+			} catch (SQLException e2) {
+				sb.append("UnExpected error: " + e2);
+			}
+		}
+
+		try {
+			// restore auto commit mode
+			con.setAutoCommit(true);
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_CisValid",
+				"Expected error: java.sql.SQLException: SELECT: no such table 'doesnotexist'\n" +
+				"Validating connection: con.isValid? true");
+	}
+
+	private void Test_Clargequery() {
+		sb.setLength(0);	// clear the output log buffer
+		final String query =
+			"-- When a query larger than the send buffer is being " +
+			"sent, a deadlock situation can occur when the server writes " +
+			"data back, blocking because we as client are sending as well " +
+			"and not reading.  Hence, to avoid this deadlock, in JDBC a " +
+			"separate thread is started in the background such that results " +
+			"from the server can be read, while data is still being sent to " +
+			"the server.  To test this, we need to trigger the SendThread " +
+			"being started, which we do with a quite large query.  We " +
+			"construct it by repeating some stupid query plus a comment " +
+			"a lot of times.  And as you're guessing by now, you're reading " +
+			"this stupid comment that we use :)\n" +
+			"select 1;\n";
+
+		final int size = 1234;
+		StringBuilder bigq = new StringBuilder(query.length() * size);
+		for (int i = 0; i < size; i++) {
+			bigq.append(query);
+		}
+
+		Statement stmt = null;
+		try {
+			// >> true: auto commit should be on by default
+			sb.append("0. true\t" + con.getAutoCommit()).append("\n");
+			stmt = con.createStatement();
+
+			// sending big script with many simple queries
+			sb.append("1. executing script").append("\n");
+			stmt.execute(bigq.toString());
+
+			int i = 1;	// we skip the first "getResultSet()"
+			while (stmt.getMoreResults() != false) {
+				i++;
+			}
+			if (stmt.getUpdateCount() != -1) {
+				sb.append("Error: found an update count for a SELECT query").append("\n");
+			}
+			if (i != size) {
+				sb.append("Error: expecting " + size + " tuples, only got " + i).append("\n");
+			}
+			sb.append("2. queries processed").append("\n");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_Clargequery",
+				"0. true	true\n" +
+				"1. executing script\n" +
+				"2. queries processed\n");
+	}
+
+	private void Test_Cmanycon(String arg0) {
+		sb.setLength(0);	// clear the output log buffer
+
+		final int maxCons = 60;	// default max_clients is 64, 2 connections are already open from this program
+		List<Connection> cons = new ArrayList<Connection>(maxCons);	// Connections go in here
+		try {
+			// spawn a lot of Connections, just for fun...
+			int i = 1;
+			sb.append("Establishing Connection ");
+			for (; i <= maxCons; i++) {
+				sb.append(i);
+				Connection conx = DriverManager.getConnection(arg0);
+				sb.append(",");
+				cons.add(conx);
+
+				// do something with the connection to test if it works
+				conx.setAutoCommit(false);
+				sb.append(" ");
+				conx.createStatement();
+			}
+			sb.append("\n");
+
+			// now try to nicely close them
+			i = 1;
+			sb.append("Closing Connection ");
+			for (Iterator<Connection> it = cons.iterator(); it.hasNext(); i++) {
+				Connection conx = it.next();
+				// see if the connection still works
+				sb.append(i);
+				conx.setAutoCommit(true);
+				sb.append(",");
+				conx.close();	// this will also implicitly close the created statement object
+				sb.append(" ");
+			}
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		compareExpectedOutput("Test_Cmanycon",
+			"Establishing Connection 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, " +
+			"11, 12, 13, 14, 15, 16, 17, 18, 19, 20, " +
+			"21, 22, 23, 24, 25, 26, 27, 28, 29, 30, " +
+			"31, 32, 33, 34, 35, 36, 37, 38, 39, 40, " +
+			"41, 42, 43, 44, 45, 46, 47, 48, 49, 50, " +
+			"51, 52, 53, 54, 55, 56, 57, 58, 59, 60, \n" +
+			"Closing Connection 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, " +
+			"11, 12, 13, 14, 15, 16, 17, 18, 19, 20, " +
+			"21, 22, 23, 24, 25, 26, 27, 28, 29, 30, " +
+			"31, 32, 33, 34, 35, 36, 37, 38, 39, 40, " +
+			"41, 42, 43, 44, 45, 46, 47, 48, 49, 50, " +
+			"51, 52, 53, 54, 55, 56, 57, 58, 59, 60, ");
+	}
+
+	private void Test_Creplysize() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt1 = null;
+		ResultSet rs = null;
+		try {
+			con.setAutoCommit(false);
+			// >> true: auto commit should be off by now
+			sb.append("0. true\t" + con.getAutoCommit()).append("\n");
+
+			stmt1 = con.createStatement();
+			// test commit by checking if a change is visible in another connection
+			sb.append("1. create... ");
+			stmt1.executeUpdate("CREATE TABLE table_Test_Creplysize ( id int )");
+			sb.append("passed").append("\n");
+
+			sb.append("2. populating with 21 records... ");
+			for (int i = 0; i < 21; i++)
+				stmt1.executeUpdate("INSERT INTO table_Test_Creplysize (id) values (" + (i + 1) + ")");
+			sb.append("passed").append("\n");
+
+			sb.append("3. hinting the driver to use fetchsize 10... ");
+			stmt1.setFetchSize(10);
+			sb.append("passed").append("\n");
+
+			sb.append("4. selecting all values... ");
+			rs = stmt1.executeQuery("SELECT * FROM table_Test_Creplysize");
+			int i = 0;
+			while (rs.next())
+				i++;
+			rs.close();
+			if (i == 21) {
+				sb.append("passed");
+			} else {
+				sb.append("got " + i + " records!!!");
+			}
+			sb.append("\n");
+
+			sb.append("5. resetting driver fetchsize hint... ");
+			stmt1.setFetchSize(0);
+			sb.append("passed").append("\n");
+
+			sb.append("6. instructing the driver to return at max 10 rows...  ");
+			stmt1.setMaxRows(10);
+			sb.append("passed").append("\n");
+
+			sb.append("7. selecting all values...  ");
+			rs = stmt1.executeQuery("SELECT * FROM table_Test_Creplysize");
+			i = 0;
+			while (rs.next())
+				i++;
+			rs.close();
+			if (i == 10) {
+				sb.append("passed");
+			} else {
+				sb.append("got " + i + " records!!!");
+			}
+			sb.append("\n");
+
+			sb.append("8. hinting the driver to use fetchsize 5... ");
+			stmt1.setFetchSize(5);
+			sb.append("passed").append("\n");
+
+			sb.append("9. selecting all values... ");
+			rs = stmt1.executeQuery("SELECT * FROM table_Test_Creplysize");
+			i = 0;
+			while (rs.next())
+				i++;
+			rs.close();
+			if (i == 10) {
+				sb.append("passed");
+			} else {
+				sb.append("got " + i + " records!!!");
+			}
+			sb.append("\n");
+
+			sb.append("10. drop... ");
+			stmt1.executeUpdate("DROP TABLE table_Test_Creplysize");
+			sb.append("passed").append("\n");
+
+			con.rollback();
+
+			// restore auto commit mode
+			con.setAutoCommit(true);
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt1, rs);
+
+		compareExpectedOutput("Test_Creplysize",
+			"0. true	false\n" +
+			"1. create... passed\n" +
+			"2. populating with 21 records... passed\n" +
+			"3. hinting the driver to use fetchsize 10... passed\n" +
+			"4. selecting all values... passed\n" +
+			"5. resetting driver fetchsize hint... passed\n" +
+			"6. instructing the driver to return at max 10 rows...  passed\n" +
+			"7. selecting all values...  passed\n" +
+			"8. hinting the driver to use fetchsize 5... passed\n" +
+			"9. selecting all values... passed\n" +
+			"10. drop... passed\n");
+	}
+
+	private void Test_Csavepoints() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		ResultSet rs = null;
+		try {
+			// >> true: auto commit should be on by default
+			sb.append("0. true\t" + con.getAutoCommit()).append("\n");
+
+			// savepoints require a non-autocommit connection
+			try {
+				sb.append("1. savepoint...");
+				con.setSavepoint();
+				sb.append("passed !!");
+			} catch (SQLException e) {
+				sb.append("expected msg: " + e.getMessage());
+			}
+			sb.append("\n");
+
+			con.setAutoCommit(false);
+			// >> true: auto commit should be on by default
+			sb.append("0. false\t" + con.getAutoCommit()).append("\n");
+
+			sb.append("2. savepoint...");
+			/* make a savepoint, and discard it */
+			con.setSavepoint();
+			sb.append("passed").append("\n");
+
+			stmt = con.createStatement();
+			stmt.executeUpdate("CREATE TABLE table_Test_Csavepoints ( id int, PRIMARY KEY (id) )");
+
+			sb.append("3. savepoint...");
+			Savepoint sp2 = con.setSavepoint("empty table");
+			sb.append("passed").append("\n");
+
+			rs = stmt.executeQuery("SELECT id FROM table_Test_Csavepoints");
+			int i = 0;
+			int items = 0;
+			sb.append("4. table " + items + " items");
+			while (rs.next()) {
+				System.out.print(", " + rs.getString("id"));
+				i++;
+			}
+			if (i != items) {
+				sb.append(" FAILED (" + i + ")");
+			}
+			sb.append(" passed").append("\n");
+
+			stmt.executeUpdate("INSERT INTO table_Test_Csavepoints VALUES (1)");
+			stmt.executeUpdate("INSERT INTO table_Test_Csavepoints VALUES (2)");
+			stmt.executeUpdate("INSERT INTO table_Test_Csavepoints VALUES (3)");
+
+			sb.append("5. savepoint...");
+			Savepoint sp3 = con.setSavepoint("three values");
+			sb.append("passed").append("\n");
+
+			rs = stmt.executeQuery("SELECT id FROM table_Test_Csavepoints");
+			i = 0;
+			items = 3;
+			sb.append("6. table " + items + " items");
+			while (rs.next()) {
+				sb.append(", " + rs.getString("id"));
+				i++;
+			}
+			if (i != items) {
+				sb.append(" FAILED (" + i + ")");
+			}
+			sb.append(" passed").append("\n");
+
+			sb.append("7. release...");
+			con.releaseSavepoint(sp3);
+			sb.append("passed").append("\n");
+
+			rs = stmt.executeQuery("SELECT id FROM table_Test_Csavepoints");
+			i = 0;
+			items = 3;
+			sb.append("8. table " + items + " items");
+			while (rs.next()) {
+				sb.append(", " + rs.getString("id"));
+				i++;
+			}
+			if (i != items) {
+				sb.append(" FAILED (" + i + ") :(");
+			}
+			sb.append(" passed").append("\n");
+
+			sb.append("9. rollback...");
+			con.rollback(sp2);
+			sb.append("passed").append("\n");
+
+			rs = stmt.executeQuery("SELECT id FROM table_Test_Csavepoints");
+			i = 0;
+			items = 0;
+			sb.append("10. table " + items + " items");
+			while (rs.next()) {
+				sb.append(", " + rs.getString("id"));
+				i++;
+			}
+			if (i != items) {
+				sb.append(" FAILED (" + i + ") :(");
+			}
+			sb.append(" passed");
+
+			con.rollback();
+
+			// restore auto commit mode
+			con.setAutoCommit(true);
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, rs);
+
+		compareExpectedOutput("Test_Csavepoints",
+			"0. true	true\n" +
+			"1. savepoint...expected msg: SAVEPOINT: not allowed in auto commit mode\n" +
+			"0. false	false\n" +
+			"2. savepoint...passed\n" +
+			"3. savepoint...passed\n" +
+			"4. table 0 items passed\n" +
+			"5. savepoint...passed\n" +
+			"6. table 3 items, 1, 2, 3 passed\n" +
+			"7. release...passed\n" +
+			"8. table 3 items, 1, 2, 3 passed\n" +
+			"9. rollback...passed\n" +
+			"10. table 0 items passed");
+	}
+
+	private void Test_Ctransaction() {
+		sb.setLength(0);	// clear the output log buffer
+
+		try {
+			// test commit by checking if a change is visible in another connection
+			sb.append("1. commit...");
+			con.commit();
+			sb.append("passed");
+		} catch (SQLException e) {
+			// this means we get what we expect
+			sb.append("failed as expected: " + e.getMessage());
+		}
+		sb.append("\n");
+
+		try {
+			// turn off auto commit
+			con.setAutoCommit(false);
+			// >> false: we just disabled it
+			sb.append("2. false\t" + con.getAutoCommit()).append("\n");
+
+			// a change would not be visible now
+			sb.append("3. commit...");
+			con.commit();
+			sb.append("passed").append("\n");
+
+			sb.append("4. commit...");
+			con.commit();
+			sb.append("passed").append("\n");
+
+			sb.append("5. rollback...");
+			con.rollback();
+			sb.append("passed");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage());
+		}
+		sb.append("\n");
+
+		Statement stmt = null;
+		try {
+			// turn off auto commit
+			con.setAutoCommit(true);
+			// >> false: we just disabled it
+			sb.append("6. true\t" + con.getAutoCommit()).append("\n");
+
+			stmt = con.createStatement();
+			sb.append("7. start transaction...");
+			stmt.executeUpdate("START TRANSACTION");
+			sb.append("passed").append("\n");
+
+			sb.append("8. commit...");
+			con.commit();
+			sb.append("passed").append("\n");
+
+			sb.append("9. true\t" + con.getAutoCommit());
+			sb.append("\n");
+
+			sb.append("10. start transaction...");
+			stmt.executeUpdate("START TRANSACTION");
+			sb.append("passed").append("\n");
+
+			sb.append("11. rollback...");
+			con.rollback();
+			sb.append("passed").append("\n");
+
+			sb.append("12. true\t" + con.getAutoCommit());
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage());
+		}
+		sb.append("\n");
+
+		try {
+			// a commit now should fail
+			sb.append("13. commit...");
+			con.commit();
+			sb.append("passed");
+		} catch (SQLException e) {
+			// this means we get what we expect
+			sb.append("failed as expected: " + e.getMessage());
+		}
+		sb.append("\n");
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_Ctransaction",
+			"1. commit...failed as expected: COMMIT: not allowed in auto commit mode\n" +
+			"2. false	false\n" +
+			"3. commit...passed\n" +
+			"4. commit...passed\n" +
+			"5. rollback...passed\n" +
+			"6. true	true\n" +
+			"7. start transaction...passed\n" +
+			"8. commit...passed\n" +
+			"9. true	true\n" +
+			"10. start transaction...passed\n" +
+			"11. rollback...passed\n" +
+			"12. true	true\n" +
+			"13. commit...failed as expected: COMMIT: not allowed in auto commit mode\n");
+	}
+
+	private void Test_Dobjects() {
+		sb.setLength(0);	// clear the output log buffer
+
+		try {
+			DatabaseMetaData dbmd = con.getMetaData();
+
+			// inspect the catalog by use of dbmd functions
+			compareResultSet(dbmd.getCatalogs(), "getCatalogs()",
+			"Resultset with 1 columns\n" +
+			"TABLE_CAT	\n");
+
+			compareResultSet(dbmd.getSchemas(null, "sys"), "getSchemas(null, sys)",
+			"Resultset with 2 columns\n" +
+			"TABLE_SCHEM	TABLE_CATALOG	\n" +
+			"sys	null	\n");
+
+			compareResultSet(dbmd.getTables(null, "tmp", null, null), "getTables(null, tmp, null, null)",	// schema tmp has 6 tables
+			"Resultset with 10 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	TABLE_TYPE	REMARKS	TYPE_CAT	TYPE_SCHEM	TYPE_NAME	SELF_REFERENCING_COL_NAME	REF_GENERATION	\n" +
+			"null	tmp	_columns	SYSTEM TABLE	null	null	null	null	null	null	\n" +
+			"null	tmp	_tables	SYSTEM TABLE	null	null	null	null	null	null	\n" +
+			"null	tmp	idxs	SYSTEM TABLE	null	null	null	null	null	null	\n" +
+			"null	tmp	keys	SYSTEM TABLE	null	null	null	null	null	null	\n" +
+			"null	tmp	objects	SYSTEM TABLE	null	null	null	null	null	null	\n" +
+			"null	tmp	triggers	SYSTEM TABLE	null	null	null	null	null	null	\n");
+
+			compareResultSet(dbmd.getTables(null, "sys", "schemas", null), "getTables(null, sys, schemas, null)",
+			"Resultset with 10 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	TABLE_TYPE	REMARKS	TYPE_CAT	TYPE_SCHEM	TYPE_NAME	SELF_REFERENCING_COL_NAME	REF_GENERATION	\n" +
+			"null	sys	schemas	SYSTEM TABLE	null	null	null	null	null	null	\n");
+
+			compareResultSet(dbmd.getColumns(null, "sys", "table_types", null), "getColumns(null, sys, table_types, null)",
+			"Resultset with 24 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	COLUMN_NAME	DATA_TYPE	TYPE_NAME	COLUMN_SIZE	BUFFER_LENGTH	DECIMAL_DIGITS	NUM_PREC_RADIX	NULLABLE	REMARKS	COLUMN_DEF	SQL_DATA_TYPE	SQL_DATETIME_SUB	CHAR_OCTET_LENGTH	ORDINAL_POSITION	IS_NULLABLE	SCOPE_CATALOG	SCOPE_SCHEMA	SCOPE_TABLE	SOURCE_DATA_TYPE	IS_AUTOINCREMENT	IS_GENERATEDCOLUMN	\n" +
+			"null	sys	table_types	table_type_id	5	smallint	16	0	0	2	0	null	null	0	0	null	1	NO	null	null	null	null	NO	NO	\n" +
+			"null	sys	table_types	table_type_name	12	varchar	25	0	0	0	0	null	null	0	0	25	2	NO	null	null	null	null	NO	NO	\n");
+
+			compareResultSet(dbmd.getPrimaryKeys(null, "sys", "table_types"), "getPrimaryKeys(null, sys, table_types)",
+			"Resultset with 6 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	COLUMN_NAME	KEY_SEQ	PK_NAME	\n" +
+			"null	sys	table_types	table_type_id	1	table_types_table_type_id_pkey	\n");
+
+			compareResultSet(dbmd.getCrossReference(null, "sys", "tables", null, "sys", "table_types"), "getCrossReference(null, sys, tables, null, sys, table_types)",
+			"Resultset with 14 columns\n" +
+			"PKTABLE_CAT	PKTABLE_SCHEM	PKTABLE_NAME	PKCOLUMN_NAME	FKTABLE_CAT	FKTABLE_SCHEM	FKTABLE_NAME	FKCOLUMN_NAME	KEY_SEQ	UPDATE_RULE	DELETE_RULE	FK_NAME	PK_NAME	DEFERRABILITY	\n");
+
+			compareResultSet(dbmd.getImportedKeys(null, "sys", "table_types"), "getImportedKeys(null, sys, table_types)",
+			"Resultset with 14 columns\n" +
+			"PKTABLE_CAT	PKTABLE_SCHEM	PKTABLE_NAME	PKCOLUMN_NAME	FKTABLE_CAT	FKTABLE_SCHEM	FKTABLE_NAME	FKCOLUMN_NAME	KEY_SEQ	UPDATE_RULE	DELETE_RULE	FK_NAME	PK_NAME	DEFERRABILITY	\n");
+
+			compareResultSet(dbmd.getIndexInfo(null, "sys", "table_types", false, false), "getIndexInfo(null, sys, table_types, false, false)",
+			"Resultset with 13 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	NON_UNIQUE	INDEX_QUALIFIER	INDEX_NAME	TYPE	ORDINAL_POSITION	COLUMN_NAME	ASC_OR_DESC	CARDINALITY	PAGES	FILTER_CONDITION	\n" +
+			"null	sys	table_types	false	null	table_types_table_type_id_pkey	2	1	table_type_id	null	10	0	null	\n" +
+			"null	sys	table_types	false	null	table_types_table_type_name_unique	2	1	table_type_name	null	10	0	null	\n");
+
+			compareResultSet(dbmd.getTablePrivileges(null, "sys", "table_types"), "getTablePrivileges(null, sys, table_types)",
+			"Resultset with 7 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	GRANTOR	GRANTEE	PRIVILEGE	IS_GRANTABLE	\n" +
+			"null	sys	table_types	monetdb	public	SELECT	NO	\n");
+
+			compareResultSet(dbmd.getColumnPrivileges(null, "sys", "table_types", null), "getColumnPrivileges(null, sys, table_types, null)",
+			"Resultset with 8 columns\n" +
+			"TABLE_CAT	TABLE_SCHEM	TABLE_NAME	COLUMN_NAME	GRANTOR	GRANTEE	PRIVILEGE	IS_GRANTABLE	\n");
+
+			compareResultSet(dbmd.getUDTs(null, "sys", null, null), "getUDTs(null, sys, null, null)",
+			"Resultset with 7 columns\n" +
+			"TYPE_CAT	TYPE_SCHEM	TYPE_NAME	CLASS_NAME	DATA_TYPE	REMARKS	BASE_TYPE	\n" +
+			"null	sys	inet	org.monetdb.jdbc.types.INET	2000	inet	null	\n" +
+			"null	sys	json	java.lang.String	2000	json	null	\n" +
+			"null	sys	url	org.monetdb.jdbc.types.URL	2000	url	null	\n" +
+			"null	sys	uuid	java.lang.String	2000	uuid	null	\n");
+
+			int[] UDTtypes = { Types.STRUCT, Types.DISTINCT };
+			compareResultSet(dbmd.getUDTs(null, "sys", null, UDTtypes), "getUDTs(null, sys, null, UDTtypes",
+			"Resultset with 7 columns\n" +
+			"TYPE_CAT	TYPE_SCHEM	TYPE_NAME	CLASS_NAME	DATA_TYPE	REMARKS	BASE_TYPE	\n");
+
+			sb.setLength(0);	// clear the output log buffer
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		compareExpectedOutput("Test_Dobjects", "");
+	}
+
+	private void compareResultSet(ResultSet rs, String methodnm, String expected) throws SQLException {
+		sb.setLength(0);	// clear the output log buffer
+
+		ResultSetMetaData rsmd = rs.getMetaData();
+		int columnCount = rsmd.getColumnCount();
+		sb.append("Resultset with ").append(columnCount).append(" columns").append("\n");
+		for (int col = 1; col <= columnCount; col++) {
+			sb.append(rsmd.getColumnName(col) + "\t");
+		}
+		sb.append("\n");
+		while (rs.next()) {
+			for (int col = 1; col <= columnCount; col++) {
+				sb.append(rs.getString(col) + "\t");
+			}
+			sb.append("\n");
+		}
+		rs.close();
+
+		compareExpectedOutput(methodnm, expected);
+	}
+
+	private void Test_FetchSize() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		ResultSet rs = null;
+		try {
+			stmt = con.createStatement();
+			rs = stmt.executeQuery("SELECT * FROM _tables");
+
+			sb.append("Statement fetch size before set: " + stmt.getFetchSize()).append("\n");
+			sb.append("ResultSet fetch size before set: " + rs.getFetchSize()).append("\n");
+
+			rs.setFetchSize(16384);
+
+			sb.append("Statement fetch size after set: " + stmt.getFetchSize()).append("\n");
+			sb.append("ResultSet fetch size after set: " + rs.getFetchSize()).append("\n");
+
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, rs);
+
+		compareExpectedOutput("Test_FetchSize",
+			"Statement fetch size before set: 0\n" +
+			"ResultSet fetch size before set: 250\n" +
+			"Statement fetch size after set: 0\n" +
+			"ResultSet fetch size after set: 16384\n");
+	}
+
+	private void Test_PSgeneratedkeys() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			con.setAutoCommit(false);
+			// >> false: auto commit was just switched off
+			sb.append("0. false\t" + con.getAutoCommit()).append("\n");
+
+			stmt = con.createStatement();
+			stmt.executeUpdate(
+				"CREATE TABLE psgenkey (" +
+				"       id       serial," +
+				"       val      varchar(20)" +
+				")");
+			stmt.close();
+		} catch (SQLException e) {
+			sb.append("FAILED to CREATE TABLE psgenkey: ").append(e.getMessage()).append("\n");
+		}
+		closeStmtResSet(stmt, null);
+
+		PreparedStatement pstmt = null;
+		ResultSet keys = null;
+		try {
+			pstmt = con.prepareStatement(
+				"INSERT INTO psgenkey (val) VALUES ('this is a test')",
+				Statement.RETURN_GENERATED_KEYS);
+
+			sb.append("1. inserting 3 records...");
+			pstmt.executeUpdate();
+			pstmt.executeUpdate();
+			pstmt.executeUpdate();
+			sb.append("success").append("\n");
+
+			// now get the generated keys
+			sb.append("2. getting generated keys...");
+			keys = pstmt.getGeneratedKeys();
+			if (keys == null) {
+				sb.append("there are no keys!").append("\n");
+			} else {
+				while (keys.next()) {
+					sb.append("generated key index: " + keys.getInt(1)).append("\n");
+				}
+				if (keys.getStatement() == null) {
+					sb.append("ResultSet.getStatement() should never return null!").append("\n");
+				}
+				keys.close();
+			}
+			pstmt.close();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+		closeStmtResSet(pstmt, keys);
+
+		try {
+			con.rollback();
+			// restore default setting
+			con.setAutoCommit(true);
+		} catch (SQLException e) {
+			sb.append("FAILED to rollback: ").append(e.getMessage()).append("\n");
+		}
+
+		compareExpectedOutput("Test_PSgeneratedkeys",
+			"0. false	false\n" +
+			"1. inserting 3 records...success\n" +
+			"2. getting generated keys...generated key index: 3\n");
+	}
+
+	private void Test_PSgetObject() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			con.setAutoCommit(false);
+			// >> false: auto commit was just switched off
+			sb.append("0. false\t" + con.getAutoCommit()).append("\n");
+
+			stmt = con.createStatement();
+			sb.append("1. creating test table...");
+			stmt.executeUpdate("CREATE TABLE table_Test_PSgetObject (ti tinyint, si smallint, i int, bi bigint)");
+			sb.append("success").append("\n");
+			stmt.close();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+		closeStmtResSet(stmt, null);
+
+		PreparedStatement pstmt = null;
+		ResultSet rs = null;
+		try {
+			sb.append("2a. inserting 3 records as batch...");
+			pstmt = con.prepareStatement("INSERT INTO table_Test_PSgetObject (ti,si,i,bi) VALUES (?,?,?,?)");
+			pstmt.setShort(1, (short)1);
+			pstmt.setShort(2, (short)1);
+			pstmt.setInt (3, 1);
+			pstmt.setLong(4, (long)1);
+			pstmt.addBatch();
+
+			pstmt.setShort(1, (short)127);
+			pstmt.setShort(2, (short)12700);
+			pstmt.setInt (3, 1270000);
+			pstmt.setLong(4, (long)127000000);
+			pstmt.addBatch();
+
+			pstmt.setShort(1, (short)-127);
+			pstmt.setShort(2, (short)-12700);
+			pstmt.setInt (3, -1270000);
+			pstmt.setLong(4, (long)-127000000);
+			pstmt.addBatch();
+
+			pstmt.executeBatch();
+			sb.append(" passed").append("\n");
+
+			sb.append("2b. closing PreparedStatement...");
+			pstmt.close();
+			sb.append(" passed").append("\n");
+		} catch (SQLException e) {
+			sb.append("FAILED to INSERT data: ").append(e.getMessage()).append("\n");
+			while ((e = e.getNextException()) != null)
+				sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		try {
+			sb.append("3a. selecting records...");
+			pstmt = con.prepareStatement("SELECT ti,si,i,bi FROM table_Test_PSgetObject ORDER BY ti,si,i,bi");
+			rs = pstmt.executeQuery();
+			sb.append(" passed").append("\n");
+
+			while (rs.next()) {
+				// test fix for https://www.monetdb.org/bugzilla/show_bug.cgi?id=4026
+				Short ti = (Short) rs.getObject(1);
+				Short si = (Short) rs.getObject(2);
+				Integer i = (Integer) rs.getObject(3);
+				Long bi = (Long) rs.getObject(4);
+
+				sb.append("  Retrieved row data: ti=" + ti + " si=" + si + " i=" + i + " bi=" + bi).append("\n");
+			}
+
+			sb.append("3b. closing ResultSet...");
+			rs.close();
+			sb.append(" passed").append("\n");
+
+			sb.append("3c. closing PreparedStatement...");
+			pstmt.close();
+			sb.append(" passed").append("\n");
+		} catch (SQLException e) {
+			sb.append("FAILED to RETRIEVE data: ").append(e.getMessage()).append("\n");
+			while ((e = e.getNextException()) != null)
+				sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+		closeStmtResSet(pstmt, rs);
+
+		try {
+			con.rollback();
+			// restore default setting
+			con.setAutoCommit(true);
+		} catch (SQLException e) {
+			sb.append("FAILED to rollback: ").append(e.getMessage()).append("\n");
+		}
+
+		compareExpectedOutput("Test_PSgetObject",
+			"0. false	false\n" +
+			"1. creating test table...success\n" +
+			"2a. inserting 3 records as batch... passed\n" +
+			"2b. closing PreparedStatement... passed\n" +
+			"3a. selecting records... passed\n" +
+			"  Retrieved row data: ti=-127 si=-12700 i=-1270000 bi=-127000000\n" +
+			"  Retrieved row data: ti=1 si=1 i=1 bi=1\n" +
+			"  Retrieved row data: ti=127 si=12700 i=1270000 bi=127000000\n" +
+			"3b. closing ResultSet... passed\n" +
+			"3c. closing PreparedStatement... passed\n");
+	}
+
+	private void Test_PSlargebatchval() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_PSlargebatchval", "");
+	}
+
+	private void Test_PSlargeresponse() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_PSlargeresponse", "");
+	}
+
+	private void Test_PSmanycon() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_PSmanycon", "");
+	}
+
+	private void Test_PSmetadata() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_PSmetadata", "");
+	}
+
+	private void Test_PSsomeamount() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_PSsomeamount", "");
+	}
+
+	private void Test_PSsqldata() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_PSsqldata", "");
+	}
+
+	private void Test_PStimedate() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_PStimedate", "");
+	}
+
+	private void Test_PStimezone() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_PStimezone", "");
+	}
+
+	private void Test_PStypes() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_PStypes", "");
+	}
+
+	private void Test_CallableStmt() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_CallableStmt", "");
+	}
+
+	private void Test_Rbooleans() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_Rbooleans", "");
+	}
+
+	private void Test_Rmetadata() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_Rmetadata", "");
+	}
+
+	private void Test_Rpositioning() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_Rpositioning", "");
+	}
+
+	private void Test_Rsqldata() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_Rsqldata", "");
+	}
+
+	private void Test_Rtimedate() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_Rtimedate", "");
+	}
+
+	private void Test_Sbatching() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			stmt = con.createStatement();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_Sbatching", "");
+	}
+
+	private void Test_Smoreresults() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		try {
+			// >> true: auto commit should be on by default
+			sb.append("0. true\t" + con.getAutoCommit()).append("\n");
+
+			stmt = con.createStatement();
+			sb.append("1. more results?...");
+			if (stmt.getMoreResults() != false || stmt.getUpdateCount() != -1)
+				sb.append("more results on an unitialised Statement, how can that be?").append("\n");
+			sb.append(" nope :)").append("\n");
+
+			sb.append("2. SELECT 1...");
+			if (stmt.execute("SELECT 1;") == false)
+				sb.append("SELECT 1 returns update or no results").append("\n");
+			sb.append(" ResultSet :)").append("\n");
+
+			sb.append("3. more results?...");
+			if (stmt.getMoreResults() != false || stmt.getUpdateCount() != -1)
+				sb.append("more results after SELECT 1 query, how can that be?").append("\n");
+			sb.append(" nope :)").append("\n");
+
+			sb.append("4. even more results?...");
+			if (stmt.getMoreResults() != false)
+				sb.append("still more results after SELECT 1 query, how can that be?").append("\n");
+			sb.append(" nope :)").append("\n");
+
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+
+		compareExpectedOutput("Test_Smoreresults",
+				"0. true	true\n" +
+				"1. more results?... nope :)\n" +
+				"2. SELECT 1... ResultSet :)\n" +
+				"3. more results?... nope :)\n" +
+				"4. even more results?... nope :)\n");
+	}
+
+	private void Test_Wrapper() {
+		sb.setLength(0);	// clear the output log buffer
+
+		try {
+			final String jdbc_pkg = "java.sql.";
+			final String monetdb_jdbc_pkg = "org.monetdb.jdbc.";
+
+			sb.append("Auto commit is: " + con.getAutoCommit()).append("\n");
+
+			checkIsWrapperFor("Connection", con, jdbc_pkg, "Connection");
+			checkIsWrapperFor("Connection", con, monetdb_jdbc_pkg, "MonetConnection");
+			checkIsWrapperFor("Connection", con, jdbc_pkg, "Statement");
+			checkIsWrapperFor("Connection", con, monetdb_jdbc_pkg, "MonetStatement");
+
+			DatabaseMetaData dbmd = con.getMetaData();
+			checkIsWrapperFor("DatabaseMetaData", dbmd, jdbc_pkg, "DatabaseMetaData");
+			checkIsWrapperFor("DatabaseMetaData", dbmd, monetdb_jdbc_pkg, "MonetDatabaseMetaData");
+			checkIsWrapperFor("DatabaseMetaData", dbmd, jdbc_pkg, "Statement");
+			checkIsWrapperFor("DatabaseMetaData", dbmd, monetdb_jdbc_pkg, "MonetStatement");
+
+			ResultSet rs = dbmd.getSchemas();
+			checkIsWrapperFor("ResultSet", rs, jdbc_pkg, "ResultSet");
+			checkIsWrapperFor("ResultSet", rs, monetdb_jdbc_pkg, "MonetResultSet");
+			checkIsWrapperFor("ResultSet", rs, jdbc_pkg, "Statement");
+			checkIsWrapperFor("ResultSet", rs, monetdb_jdbc_pkg, "MonetStatement");
+
+			ResultSetMetaData rsmd = rs.getMetaData();
+			checkIsWrapperFor("ResultSetMetaData", rsmd, jdbc_pkg, "ResultSetMetaData");
+			checkIsWrapperFor("ResultSetMetaData", rsmd, monetdb_jdbc_pkg, "MonetResultSet");
+			checkIsWrapperFor("ResultSetMetaData", rsmd, monetdb_jdbc_pkg, "MonetResultSet$rsmdw");  // it is a private class of MonetResultSet
+			checkIsWrapperFor("ResultSetMetaData", rsmd, jdbc_pkg, "Statement");
+			checkIsWrapperFor("ResultSetMetaData", rsmd, monetdb_jdbc_pkg, "MonetStatement");
+
+			rs.close();
+
+			Statement stmt = con.createStatement();
+			checkIsWrapperFor("Statement", stmt, jdbc_pkg, "Statement");
+			checkIsWrapperFor("Statement", stmt, monetdb_jdbc_pkg, "MonetStatement");
+			checkIsWrapperFor("Statement", stmt, jdbc_pkg, "Connection");
+			checkIsWrapperFor("Statement", stmt, monetdb_jdbc_pkg, "MonetConnection");
+
+			stmt.close();
+
+			PreparedStatement pstmt = con.prepareStatement("SELECT name FROM sys.tables WHERE system AND name like ?");
+			checkIsWrapperFor("PreparedStatement", pstmt, jdbc_pkg, "PreparedStatement");
+			checkIsWrapperFor("PreparedStatement", pstmt, monetdb_jdbc_pkg, "MonetPreparedStatement");
+			checkIsWrapperFor("PreparedStatement", pstmt, jdbc_pkg, "Statement");
+			checkIsWrapperFor("PreparedStatement", pstmt, monetdb_jdbc_pkg, "MonetStatement");
+			checkIsWrapperFor("PreparedStatement", pstmt, jdbc_pkg, "Connection");
+			checkIsWrapperFor("PreparedStatement", pstmt, monetdb_jdbc_pkg, "MonetConnection");
+
+			ParameterMetaData pmd = pstmt.getParameterMetaData();
+			checkIsWrapperFor("ParameterMetaData", pmd, jdbc_pkg, "ParameterMetaData");
+			checkIsWrapperFor("ParameterMetaData", pmd, monetdb_jdbc_pkg, "MonetPreparedStatement");
+			checkIsWrapperFor("ParameterMetaData", pmd, monetdb_jdbc_pkg, "MonetPreparedStatement$pmdw");  // it is a private class of MonetPreparedStatement
+			checkIsWrapperFor("ParameterMetaData", pmd, jdbc_pkg, "Connection");
+			checkIsWrapperFor("ParameterMetaData", pmd, monetdb_jdbc_pkg, "MonetConnection");
+
+			ResultSetMetaData psrsmd = pstmt.getMetaData();
+			checkIsWrapperFor("PrepStmt ResultSetMetaData", psrsmd, jdbc_pkg, "ResultSetMetaData");
+			checkIsWrapperFor("PrepStmt ResultSetMetaData", psrsmd, monetdb_jdbc_pkg, "MonetPreparedStatement");
+			checkIsWrapperFor("PrepStmt ResultSetMetaData", psrsmd, monetdb_jdbc_pkg, "MonetPreparedStatement$rsmdw");  // it is a private class of MonetPreparedStatement
+			checkIsWrapperFor("PrepStmt ResultSetMetaData", psrsmd, jdbc_pkg, "Connection");
+			checkIsWrapperFor("PrepStmt ResultSetMetaData", psrsmd, monetdb_jdbc_pkg, "MonetConnection");
+
+			pstmt.close();
+
+		} catch (SQLException e) {
+			while ((e = e.getNextException()) != null)
+				sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		compareExpectedOutput("Test_Wrapper", "Auto commit is: true\n" +
+				"Connection. isWrapperFor(Connection) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"Connection. isWrapperFor(MonetConnection) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"Connection. isWrapperFor(Statement) returns: false\n" +
+				"Connection. isWrapperFor(MonetStatement) returns: false\n" +
+				"DatabaseMetaData. isWrapperFor(DatabaseMetaData) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"DatabaseMetaData. isWrapperFor(MonetDatabaseMetaData) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"DatabaseMetaData. isWrapperFor(Statement) returns: false\n" +
+				"DatabaseMetaData. isWrapperFor(MonetStatement) returns: false\n" +
+				"ResultSet. isWrapperFor(ResultSet) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"ResultSet. isWrapperFor(MonetResultSet) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"ResultSet. isWrapperFor(Statement) returns: false\n" +
+				"ResultSet. isWrapperFor(MonetStatement) returns: false\n" +
+				"ResultSetMetaData. isWrapperFor(ResultSetMetaData) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"ResultSetMetaData. isWrapperFor(MonetResultSet) returns: false\n" +
+				"ResultSetMetaData. isWrapperFor(MonetResultSet$rsmdw) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"ResultSetMetaData. isWrapperFor(Statement) returns: false\n" +
+				"ResultSetMetaData. isWrapperFor(MonetStatement) returns: false\n" +
+				"Statement. isWrapperFor(Statement) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"Statement. isWrapperFor(MonetStatement) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"Statement. isWrapperFor(Connection) returns: false\n" +
+				"Statement. isWrapperFor(MonetConnection) returns: false\n" +
+				"PreparedStatement. isWrapperFor(PreparedStatement) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"PreparedStatement. isWrapperFor(MonetPreparedStatement) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"PreparedStatement. isWrapperFor(Statement) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"PreparedStatement. isWrapperFor(MonetStatement) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"PreparedStatement. isWrapperFor(Connection) returns: false\n" +
+				"PreparedStatement. isWrapperFor(MonetConnection) returns: false\n" +
+				"ParameterMetaData. isWrapperFor(ParameterMetaData) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"ParameterMetaData. isWrapperFor(MonetPreparedStatement) returns: false\n" +
+				"ParameterMetaData. isWrapperFor(MonetPreparedStatement$pmdw) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"ParameterMetaData. isWrapperFor(Connection) returns: false\n" +
+				"ParameterMetaData. isWrapperFor(MonetConnection) returns: false\n" +
+				"PrepStmt ResultSetMetaData. isWrapperFor(ResultSetMetaData) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"PrepStmt ResultSetMetaData. isWrapperFor(MonetPreparedStatement) returns: false\n" +
+				"PrepStmt ResultSetMetaData. isWrapperFor(MonetPreparedStatement$rsmdw) returns: true	Called unwrap(). Returned object is not null, so oke\n" +
+				"PrepStmt ResultSetMetaData. isWrapperFor(Connection) returns: false\n" +
+				"PrepStmt ResultSetMetaData. isWrapperFor(MonetConnection) returns: false\n");
+	}
+
+	private void checkIsWrapperFor(String objnm, Wrapper obj, String pkgnm, String classnm) {
+		try {
+			Class<?> clazz = Class.forName(pkgnm + classnm);
+			boolean isWrapper = obj.isWrapperFor(clazz);
+			sb.append(objnm + ". isWrapperFor(" + classnm + ") returns: " + isWrapper);
+			if (isWrapper) {
+				Object wobj = obj.unwrap(clazz);
+				sb.append("\tCalled unwrap(). Returned object is " + (wobj != null ? "not null, so oke" : "null !!"));
+			}
+			sb.append("\n");
+		} catch (ClassNotFoundException cnfe) {
+			sb.append(cnfe.toString());
+		} catch (SQLException se) {
+			sb.append(se.getMessage());
+		}
+	}
+
+	private void compareExpectedOutput(String testname, String expected) {
+		if (expected == null || sb == null) {
+			System.out.println("Cannot compare for " + testname + ": expected == null || sb == null");
+		} else
+		if (!expected.equals(sb.toString())) {
+			System.out.println("Test '" + testname + "()' produced different output! Expected:");
+			System.out.println(expected);
+			System.out.println("Gotten:");
+			System.out.println(sb);
+			System.out.println();
+		}
+	}
+
+	private void closeConx(Connection cn) {
+		if (cn != null) {
+			try {
+				cn.close();
+			} catch (SQLException e) { /* ignore */ }
+		}
+	}
+
+	private void closeStmtResSet(Statement st, ResultSet rs) {
+		if (rs != null) {
+			try {
+				rs.close();
+			} catch (SQLException e) { /* ignore */ }
+		}
+		if (st != null) {
+			try {
+				st.close();
+			} catch (SQLException e) { /* ignore */ }
+		}
+	}
+}
--- a/tests/build.xml
+++ b/tests/build.xml
@@ -84,7 +84,7 @@ Copyright 1997 - July 2008 CWI, August 2
   <target name="find_driver">
     <condition property="have_driver">
       <and>
-        <available classname="nl.cwi.monetdb.jdbc.MonetDriver">
+        <available classname="org.monetdb.jdbc.MonetDriver">
           <classpath>
             <pathelement path="${jdbc_jar}" />
           </classpath>
@@ -101,6 +101,7 @@ Copyright 1997 - July 2008 CWI, August 2
   <!-- Run tests -->
   <target name="test">
     <antcall target="SQLcopyinto" />
+    <antcall target="JDBC_API_Tester" />
     <antcall target="Test_Cautocommit" />
     <!-- <antcall target="Test_Cforkbomb" /> -->
     <antcall target="Test_CisValid" />
@@ -167,6 +168,12 @@ Copyright 1997 - July 2008 CWI, August 2
     </antcall>
   </target>
 
+  <target name="JDBC_API_Tester">
+    <antcall target="test_class">
+      <param name="test.class" value="JDBC_API_Tester" />
+    </antcall>
+  </target>
+
   <target name="Test_CallableStmt">
     <antcall target="test_class">
       <param name="test.class" value="Test_CallableStmt" />