changeset 412:42e006ee81a4

Merge branch 'master' into ci
author GIT repo for MonetDB <monetgit@dev.monetdb.org>
date Mon, 11 Jan 2021 16:20:02 +0100 (2021-01-11)
parents 2c1bda502efa (current diff) 5540793628d6 (diff)
children f0dfd2003aff
files
diffstat 100 files changed, 3569 insertions(+), 380 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore
+++ b/.hgignore
@@ -4,7 +4,7 @@ syntax: glob
 *.class
 *.jar
 build/META-INF/services/java.sql.Driver
-src/main/java/nl/cwi/monetdb/jdbc/MonetDriver.java
+src/main/java/org/monetdb/jdbc/MonetDriver.java
 
 # files generated by various editors
 *.swp
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,17 @@
 # ChangeLog file for monetdb-java
 # This file is updated with Maddlog
 
+* Wed Jan  6 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
+- Corrected output of resultset columns UPDATE_RULE and DELETE_RULE
+  when calling DatabaseMetaData API methods getImportedKeys() or
+  getExportedKeys() or getCrossReference(). These columns used to
+  always return DatabaseMetaData.importedKeyNoAction but now they
+  can also report the other values when set:
+     DatabaseMetaData.importedKeyCascade
+  or DatabaseMetaData.importedKeyRestrict
+  or DatabaseMetaData.importedKeySetNull
+  or DatabaseMetaData.importedKeySetDefault.
+
 * Thu Nov 12 2020 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
 - Moved Java classes from packages starting with nl.cwi.monetdb.*
   to package org.monetdb.* This naming complies to the Java Package
@@ -41,7 +52,7 @@
 
 * Thu Oct  8 2020 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
 - Improved performance of ResultSetMetaData methods isAutoIncrement(),
-  getPrecision() and getScale() significant for columns of specific data
+  getPrecision() and getScale() significantly for columns of specific data
   types as in some cases no costly meta data query is executed anymore.
 - The connection properties  treat_clob_as_varchar  and  treat_blob_as_binary
   are now set to true by default within the JDBC driver.  This is done
--- a/Makefile
+++ b/Makefile
@@ -11,3 +11,11 @@ doc:
 clean:
 	rm -f src/main/java/org/monetdb/jdbc/MonetDriver.java
 	rm -rf build tests/build jars doc
+
+cleandoc:
+	rm -rf doc
+
+cleantests:
+	rm -rf tests/build
+	rm -f  jars/jdbctests.jar
+
--- a/SQLSTATEs
+++ b/SQLSTATEs
@@ -53,7 +53,7 @@ M1M20 object closed
 M1M25 failed reading from/writing to object stream
 
 SQLState codes are used in SQLExceptions.
-JDBC 4.1 defines the following SQLException subclass mappings:
+JDBC 4.2 defines the following SQLException subclass mappings:
  NonTransientSQLExceptions (fails when same operation executed again)
    0A SQLFeatureNotSupportedException
    08 SQLNonTransientConnectionException
@@ -66,6 +66,6 @@ JDBC 4.1 defines the following SQLExcept
    40 SQLTransactionRollbackException
    null SQLTimeoutException
 
-See also: http://docs.oracle.com/javase/7/docs/api/java/sql/SQLException.html
+See also: http://docs.oracle.com/javase/8/docs/api/java/sql/SQLException.html
 See also: https://en.wikipedia.org/wiki/SQLSTATE
 
--- a/build.xml
+++ b/build.xml
@@ -5,7 +5,7 @@ This Source Code Form is subject to the 
 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.
+Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
 -->
 
 <!--
--- a/example/MJDBCTest.java
+++ b/example/MJDBCTest.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/example/PreparedExample.java
+++ b/example/PreparedExample.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/example/SQLImport.java
+++ b/example/SQLImport.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/src/main/java/nl/cwi/monetdb/client/JdbcClient.java
+++ b/src/main/java/nl/cwi/monetdb/client/JdbcClient.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package nl.cwi.monetdb.client;
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetDriver.java
+++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetDriver.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package nl.cwi.monetdb.jdbc;
--- a/src/main/java/nl/cwi/monetdb/jdbc/types/INET.java
+++ b/src/main/java/nl/cwi/monetdb/jdbc/types/INET.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package nl.cwi.monetdb.jdbc.types;
--- a/src/main/java/nl/cwi/monetdb/jdbc/types/URL.java
+++ b/src/main/java/nl/cwi/monetdb/jdbc/types/URL.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package nl.cwi.monetdb.jdbc.types;
--- a/src/main/java/nl/cwi/monetdb/mcl/net/MapiSocket.java
+++ b/src/main/java/nl/cwi/monetdb/mcl/net/MapiSocket.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package nl.cwi.monetdb.mcl.net;
--- a/src/main/java/org/monetdb/client/JMonetDB.java
+++ b/src/main/java/org/monetdb/client/JMonetDB.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.client;
--- a/src/main/java/org/monetdb/client/JdbcClient.java
+++ b/src/main/java/org/monetdb/client/JdbcClient.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.client;
--- a/src/main/java/org/monetdb/jdbc/MonetBlob.java
+++ b/src/main/java/org/monetdb/jdbc/MonetBlob.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.jdbc;
--- a/src/main/java/org/monetdb/jdbc/MonetCallableStatement.java
+++ b/src/main/java/org/monetdb/jdbc/MonetCallableStatement.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.jdbc;
--- a/src/main/java/org/monetdb/jdbc/MonetClob.java
+++ b/src/main/java/org/monetdb/jdbc/MonetClob.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.jdbc;
--- a/src/main/java/org/monetdb/jdbc/MonetConnection.java
+++ b/src/main/java/org/monetdb/jdbc/MonetConnection.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.jdbc;
@@ -76,7 +76,7 @@ public class MonetConnection
 	/** The hostname to connect to */
 	private final String hostname;
 	/** The port to connect on the host to */
-	private int port = 0;
+	private int port;
 	/** The database to use (currently not used) */
 	private final String database;
 	/** The username to use when authenticating */
@@ -101,7 +101,7 @@ public class MonetConnection
 	private boolean autoCommit = true;
 
 	/** The stack of warnings for this Connection object */
-	private SQLWarning warnings = null;
+	private SQLWarning warnings;
 
 	/** The Connection specific mapping of user defined types to Java types */
 	private Map<String,Class<?>> typeMap = new HashMap<String,Class<?>>() {
@@ -142,7 +142,7 @@ public class MonetConnection
 	private boolean treatClobAsVarChar = true;
 
 	/** The last set query timeout on the server as used by Statement, PreparedStatement and CallableStatement */
-	protected int lastSetQueryTimeout = 0;	// 0 means no timeout, which is the default on the server
+	protected int lastSetQueryTimeout;	// 0 means no timeout, which is the default on the server
 
 
 	/**
@@ -1014,7 +1014,7 @@ public class MonetConnection
 	public void setAutoCommit(final boolean autoCommit) throws SQLException {
 		checkNotClosed();
 		if (this.autoCommit != autoCommit) {
-			sendControlCommand("auto_commit " + (autoCommit ? "1" : "0"));
+			sendControlCommand(autoCommit ? "auto_commit 1" : "auto_commit 0");
 			this.autoCommit = autoCommit;
 		}
 	}
@@ -1074,14 +1074,7 @@ public class MonetConnection
 	 */
 	@Override
 	public Savepoint setSavepoint() throws SQLException {
-		checkNotClosed();
-		// create a new Savepoint object
-		final MonetSavepoint sp = new MonetSavepoint();
-
-		// note: can't use sendIndependentCommand here because we need
-		// to process the auto_commit state the server gives
-		sendTransactionCommand("SAVEPOINT " + sp.getName());
-		return sp;
+		return setSavepoint(null);
 	}
 
 	/**
@@ -1099,7 +1092,7 @@ public class MonetConnection
 		// create a new Savepoint object
 		final MonetSavepoint sp;
 		try {
-			sp = new MonetSavepoint(name);
+			sp = (name != null) ? new MonetSavepoint(name) : new MonetSavepoint();
 		} catch (IllegalArgumentException e) {
 			throw new SQLException(e.getMessage(), "M0M03");
 		}
@@ -1318,7 +1311,7 @@ public class MonetConnection
 				}
 			}
 		} catch (SQLException se) {
-			String msg = se.getMessage();
+			final String msg = se.getMessage();
 			// System.out.println(se.getSQLState() + " Con.isValid(): " + msg);
 			if (msg != null && msg.equalsIgnoreCase("Current transaction is aborted (please ROLLBACK)")) {
 				// Must use equalsIgnoreCase() here because up to Jul2017 release 'Current' was 'current' so with lowercase c.
@@ -1328,22 +1321,18 @@ public class MonetConnection
 			}
 			/* ignore stmt errors/exceptions, we are only testing if the connection is still alive and usable */
 		} finally {
+			closeResultsetStatement(rs, stmt);
 			/* when changed, reset the original server timeout value on the server */
 			if (timeout > 0 && original_timeout != this.lastSetQueryTimeout) {
 				this.lastSetQueryTimeout = original_timeout;
-				Statement stmt2 = null;
 				try {
 					/* we have to set in the server explicitly, because the test 'queryTimeout != connection.lastSetQueryTimeout' 
 					   on MonetStatement.internalExecute(sql) won't pass and the server won't be set back */
-					stmt2 = this.createStatement();
-					stmt2.execute("CALL \"sys\".\"settimeout\"(" + this.lastSetQueryTimeout + ")");
+					setQueryTimeout(original_timeout);
 				} catch (SQLException se) {
 					/* ignore stmt errors/exceptions, we are only testing if the connection is still alive and usable */
-				} finally {
-					closeResultsetStatement(null, stmt2);
 				}
 			}
-			closeResultsetStatement(rs, stmt);
 		}
 		return isValid;
 	}
@@ -1643,6 +1632,43 @@ public class MonetConnection
 	//== internal helper methods which do not belong to the JDBC interface
 
 	/**
+	 * Local helper method to test whether the Connection object is closed
+	 * When closed it throws an SQLException
+	 */
+	private void checkNotClosed() throws SQLException {
+		if (closed)
+			throw new SQLException("Connection is closed", "M1M20");
+	}
+
+	/**
+	 * Utility method to call sys.setquerytimeout(int); procedure on the connected server.
+	 * It is called from: MonetConnection.isValid() and MonetStatement.internalExecute()
+	 */
+	void setQueryTimeout(final int millis) throws SQLException {
+		if (millis < 0)
+			throw new SQLException("query timeout milliseconds is less than zero", "M1M05");
+
+		checkNotClosed();
+		Statement st = null;
+		try {
+			// as of release Jun2020 (11.37.7) the function sys.settimeout(bigint) is deprecated and replaced by new sys.setquerytimeout(int)
+			final boolean postJun2020 = (getDatabaseMajorVersion() >=11) && (getDatabaseMinorVersion() >= 37);
+			final String callstmt = postJun2020 ? "CALL sys.\"setquerytimeout\"(" + millis + ")"
+							    : "CALL sys.\"settimeout\"(" + millis + ")";
+			// for debug: System.out.println("Before: " + callstmt);
+			st = createStatement();
+			st.execute(callstmt);
+			// for debug: System.out.println("After : " + callstmt);
+
+			this.lastSetQueryTimeout = millis;
+		}
+		/* do not catch SQLException here, as we want to know it when it fails */
+		finally {
+			closeResultsetStatement(null, st);
+		}
+	}
+
+	/**
 	 * @return whether the JDBC BLOB type should be mapped to VARBINARY type.
 	 * This allows generic JDBC programs to fetch Blob data via getBytes()
 	 * instead of getBlob() and Blob.getBinaryStream() to reduce overhead.
@@ -1663,15 +1689,6 @@ public class MonetConnection
 	}
 
 	/**
-	 * Local helper method to test whether the Connection object is closed
-	 * When closed it throws an SQLException
-	 */
-	private void checkNotClosed() throws SQLException {
-		if (closed)
-			throw new SQLException("Connection is closed", "M1M20");
-	}
-
-	/**
 	 * @return the MonetDB JDBC Connection URL (without user name and password).
 	 * It is called from: getURL()in MonetDatabaseMetaData
 	 */
@@ -1702,10 +1719,10 @@ public class MonetConnection
 	}
 
 
-	// Internal cache for 3 static mserver environment values, so they aren't queried from mserver again and again
-	private String env_current_user = null;
-	private String env_monet_version = null;
-	private String env_max_clients = null;
+	// Internal caches for 3 static mserver environment values, so they aren't queried from mserver again and again
+	private String env_current_user;
+	private String env_monet_version;
+	private int maxConnections;
 
 	/**
 	 * Utility method to fetch 3 mserver environment values combined in one query for efficiency.
@@ -1732,8 +1749,14 @@ public class MonetConnection
 						if ("monet_version".equals(prop)) {
 							env_monet_version = value;
 						} else
-						if ("max_clients".equals(prop)) {
-							env_max_clients = value;
+						if ("max_clients".equals(prop) && value != null) {
+							try {
+								maxConnections = Integer.parseInt(value);
+							} catch (NumberFormatException nfe) {
+								/* ignore */
+							}
+							if (maxConnections <= 0)
+								maxConnections = 1;
 						}
 					}
 				}
@@ -1742,7 +1765,7 @@ public class MonetConnection
 		} finally {
 			closeResultsetStatement(rs, st);
 		}
-		// for debug: System.out.println("Read: env_current_user: " + env_current_user + "  env_monet_version: " + env_monet_version + "  env_max_clients: " + env_max_clients);
+		// for debug: System.out.println("Read: env_current_user: " + env_current_user + "  env_monet_version: " + env_monet_version + "  env_max_clients: " + maxConnections);
 	}
 
 	/**
@@ -1756,6 +1779,17 @@ public class MonetConnection
 	}
 
 	/**
+	 * @return the maximum number of active connections possible at one time;
+	 * a result of zero means that there is no limit or the limit is not known
+	 * It is called from: MonetDatabaseMetaData
+	 */
+	int getMaxConnections() throws SQLException {
+		if (maxConnections == 0)
+			getEnvValues();
+		return maxConnections;
+	}
+
+	/**
 	 * @return the MonetDB Database Server version string.
 	 * It is called from: MonetDatabaseMetaData
 	 */
@@ -1768,64 +1802,54 @@ public class MonetConnection
 		return "";
 	}
 
+	private int databaseMajorVersion;
 	/**
 	 * @return the MonetDB Database Server major version number.
-	 * It is called from: MonetDatabaseMetaData
+	 * The number is extracted from the env_monet_version the first time and cached for next calls.
+	 * It is called from: MonetDatabaseMetaData and MonetConnection
 	 */
 	int getDatabaseMajorVersion() throws SQLException {
-		if (env_monet_version == null)
-			getEnvValues();
-		if (env_monet_version != null) {
-			try {
-				// from version string such as 11.33.9 extract number: 11
-				final int start = env_monet_version.indexOf('.');
-				return Integer.parseInt((start >= 0) ? env_monet_version.substring(0, start) : env_monet_version);
-			} catch (NumberFormatException nfe) {
-				// ignore
+		if (databaseMajorVersion == 0) {
+			if (env_monet_version == null)
+				getEnvValues();
+			if (env_monet_version != null) {
+				try {
+					// from version string such as 11.33.9 extract number: 11
+					final int start = env_monet_version.indexOf('.');
+					databaseMajorVersion = Integer.parseInt((start >= 0) ? env_monet_version.substring(0, start) : env_monet_version);
+				} catch (NumberFormatException nfe) {
+					// ignore
+				}
 			}
 		}
-		return 0;
+		return databaseMajorVersion;
 	}
 
+	private int databaseMinorVersion;
 	/**
 	 * @return the MonetDB Database Server minor version number.
-	 * It is called from: MonetDatabaseMetaData
+	 * The number is extracted from the env_monet_version the first time and cached for next calls.
+	 * It is called from: MonetDatabaseMetaData and MonetConnection
 	 */
 	int getDatabaseMinorVersion() throws SQLException {
-		if (env_monet_version == null)
-			getEnvValues();
-		if (env_monet_version != null) {
-			try {
-				// from version string such as 11.33.9 extract number: 33
-				int start = env_monet_version.indexOf('.');
-				if (start >= 0) {
-					start++;
-					final int end = env_monet_version.indexOf('.', start);
-					return Integer.parseInt((end > 0) ? env_monet_version.substring(start, end) : env_monet_version.substring(start));
+		if (databaseMinorVersion == 0) {
+			if (env_monet_version == null)
+				getEnvValues();
+			if (env_monet_version != null) {
+				try {
+					// from version string such as 11.33.9 extract number: 33
+					int start = env_monet_version.indexOf('.');
+					if (start >= 0) {
+						start++;
+						final int end = env_monet_version.indexOf('.', start);
+						databaseMinorVersion = Integer.parseInt((end > 0) ? env_monet_version.substring(start, end) : env_monet_version.substring(start));
+					}
+				} catch (NumberFormatException nfe) {
+					// ignore
 				}
-			} catch (NumberFormatException nfe) {
-				// ignore
 			}
 		}
-		return 0;
-	}
-
-	/**
-	 * @return the maximum number of active connections possible at one time;
-	 * a result of zero means that there is no limit or the limit is not known
-	 * It is called from: MonetDatabaseMetaData
-	 */
-	int getMaxConnections() throws SQLException {
-		if (env_max_clients == null)
-			getEnvValues();
-		if (env_max_clients != null) {
-			try {
-				return Integer.parseInt(env_max_clients);
-			} catch (NumberFormatException nfe) {
-				/* ignore */
-			}
-		}
-		return 0;
+		return databaseMinorVersion;
 	}
 
 
@@ -2092,17 +2116,17 @@ public class MonetConnection
 		 *  if we close this Response */
 		private boolean destroyOnClose;
 		/** the offset to be used on Xexport queries */
-		private int blockOffset = 0;
+		private int blockOffset;
 
 		/** A parser for header lines */
 		private final HeaderLineParser hlp;
 
 		/** A boolean array telling whether the headers are set or not */
 		private final boolean[] isSet;
-		private static final int NAMES	= 0;
-		private static final int TYPES	= 1;
-		private static final int TABLES	= 2;
-		private static final int LENS	= 3;
+		private static final int NAMES  = 0;
+		private static final int TYPES  = 1;
+		private static final int TABLES = 2;
+		private static final int LENS   = 3;
 
 
 		/**
--- a/src/main/java/org/monetdb/jdbc/MonetDataSource.java
+++ b/src/main/java/org/monetdb/jdbc/MonetDataSource.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.jdbc;
--- a/src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java
+++ b/src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.jdbc;
@@ -2588,11 +2588,25 @@ public class MonetDatabaseMetaData
 		"fktable.\"name\" AS \"FKTABLE_NAME\", " +
 		"fkkeycol.\"name\" AS \"FKCOLUMN_NAME\", " +
 		"cast(1 + \"pkkeycol\".\"nr\" AS smallint) AS \"KEY_SEQ\", " +
-		DatabaseMetaData.importedKeyNoAction + " AS \"UPDATE_RULE\", " +
-		DatabaseMetaData.importedKeyNoAction + " AS \"DELETE_RULE\", " +
+		"cast(CASE ((fkkey.\"action\" >> 8) & 255)" +	// logic pulled from clients/mapiclient/dump.c dump_foreign_keys()
+			" WHEN 0 THEN " + DatabaseMetaData.importedKeyNoAction +
+			" WHEN 1 THEN " + DatabaseMetaData.importedKeyCascade +
+			" WHEN 2 THEN " + DatabaseMetaData.importedKeyRestrict +
+			" WHEN 3 THEN " + DatabaseMetaData.importedKeySetNull +
+			" WHEN 4 THEN " + DatabaseMetaData.importedKeySetDefault +
+			" ELSE " + DatabaseMetaData.importedKeyNoAction +
+			" END AS smallint) AS \"UPDATE_RULE\", " +
+		"cast(CASE (fkkey.\"action\" & 255)" +	// logic pulled from clients/mapiclient/dump.c dump_foreign_keys()
+			" WHEN 0 THEN " + DatabaseMetaData.importedKeyNoAction +
+			" WHEN 1 THEN " + DatabaseMetaData.importedKeyCascade +
+			" WHEN 2 THEN " + DatabaseMetaData.importedKeyRestrict +
+			" WHEN 3 THEN " + DatabaseMetaData.importedKeySetNull +
+			" WHEN 4 THEN " + DatabaseMetaData.importedKeySetDefault +
+			" ELSE " + DatabaseMetaData.importedKeyNoAction +
+			" END AS smallint) AS \"DELETE_RULE\", " +
 		"fkkey.\"name\" AS \"FK_NAME\", " +
 		"pkkey.\"name\" AS \"PK_NAME\", " +
-		DatabaseMetaData.importedKeyNotDeferrable + " AS \"DEFERRABILITY\" " +
+		"cast(" + DatabaseMetaData.importedKeyNotDeferrable + " AS smallint) AS \"DEFERRABILITY\" " +
 	"FROM \"sys\".\"keys\" pkkey " +
 	"JOIN \"sys\".\"objects\" pkkeycol ON pkkey.\"id\" = pkkeycol.\"id\" " +
 	"JOIN \"sys\".\"tables\" pktable ON pktable.\"id\" = pkkey.\"table_id\" " +
@@ -2625,26 +2639,21 @@ public class MonetDatabaseMetaData
 	 *	<LI><B>FKCOLUMN_NAME</B> String =&gt; foreign key column name
 	 *	<LI><B>KEY_SEQ</B> short =&gt; sequence number within foreign key
 	 *		(a value of 1 represents the first column of the foreign key, a value of 2 would represent the second column within the foreign key).
-	 *	<LI><B>UPDATE_RULE</B> short =&gt; What happens to
-	 *		 foreign key when primary is updated:
+	 *	<LI><B>UPDATE_RULE</B> short =&gt; What happens to foreign key when primary is updated:
 	 *		<UL>
 	 *		<LI> importedKeyNoAction - do not allow update of primary key if it has been imported
-	 *		<LI> importedKeyCascade - change imported key to agree
-	 *				 with primary key update
-	 *		<LI> importedKeyRestrict - do not allow update of primary
-	 *				 key if it has been imported
-	 *		<LI> importedKeySetNull - change imported key to NULL if
-	 *				 its primary key has been updated
+	 *		<LI> importedKeyCascade - change imported key to agree with primary key update
+	 *		<LI> importedKeyRestrict - same as importedKeyNoAction (for ODBC 2.x compatibility)
+	 *		<LI> importedKeySetNull - change imported key to NULL if its primary key has been updated
+	 *		<LI> importedKeySetDefault - change imported key to the default values if its primary key has been updated
 	 *		</UL>
-	 *	<LI><B>DELETE_RULE</B> short =&gt; What happens to
-	 *		the foreign key when primary is deleted.
+	 *	<LI><B>DELETE_RULE</B> short =&gt; What happens to the foreign key when primary is deleted.
 	 *		<UL>
 	 *		<LI> importedKeyNoAction - do not allow delete of primary key if it has been imported
 	 *		<LI> importedKeyCascade - delete rows that import a deleted key
-	 *		<LI> importedKeyRestrict - do not allow delete of primary
-	 *				 key if it has been imported
-	 *		<LI> importedKeySetNull - change imported key to NULL if
-	 *				 its primary key has been deleted
+	 *		<LI> importedKeyRestrict - same as importedKeyNoAction (for ODBC 2.x compatibility)
+	 *		<LI> importedKeySetNull - change imported key to NULL if its primary key has been deleted
+	 *		<LI> importedKeySetDefault - change imported key to the default values if its primary key has been deleted
 	 *		</UL>
 	 *	<LI><B>FK_NAME</B> String =&gt; foreign key name (may be null)
 	 *	<LI><B>PK_NAME</B> String =&gt; primary key name (may be null)
@@ -2713,26 +2722,21 @@ public class MonetDatabaseMetaData
 	 *		being exported
 	 *	<LI><B>KEY_SEQ</B> short =&gt; sequence number within foreign key
 	 *		(a value of 1 represents the first column of the foreign key, a value of 2 would represent the second column within the foreign key).
-	 *	<LI><B>UPDATE_RULE</B> short =&gt; What happens to
-	 *		 foreign key when primary is updated:
+	 *	<LI><B>UPDATE_RULE</B> short =&gt; What happens to foreign key when primary is updated:
 	 *		<UL>
 	 *		<LI> importedKeyNoAction - do not allow update of primary key if it has been imported
-	 *		<LI> importedKeyCascade - change imported key to agree
-	 *				 with primary key update
-	 *		<LI> importedKeyRestrict - do not allow update of primary
-	 *				 key if it has been imported
-	 *		<LI> importedKeySetNull - change imported key to NULL if
-	 *				 its primary key has been updated
+	 *		<LI> importedKeyCascade - change imported key to agree with primary key update
+	 *		<LI> importedKeyRestrict - same as importedKeyNoAction (for ODBC 2.x compatibility)
+	 *		<LI> importedKeySetNull - change imported key to NULL if its primary key has been updated
+	 *		<LI> importedKeySetDefault - change imported key to the default values if its primary key has been updated
 	 *		</UL>
-	 *	<LI><B>DELETE_RULE</B> short =&gt; What happens to
-	 *		the foreign key when primary is deleted.
+	 *	<LI><B>DELETE_RULE</B> short =&gt; What happens to the foreign key when primary is deleted.
 	 *		<UL>
 	 *		<LI> importedKeyNoAction - do not allow delete of primary key if it has been imported
 	 *		<LI> importedKeyCascade - delete rows that import a deleted key
-	 *		<LI> importedKeyRestrict - do not allow delete of primary
-	 *				 key if it has been imported
-	 *		<LI> importedKeySetNull - change imported key to NULL if
-	 *				 its primary key has been deleted
+	 *		<LI> importedKeyRestrict - same as importedKeyNoAction (for ODBC 2.x compatibility)
+	 *		<LI> importedKeySetNull - change imported key to NULL if its primary key has been deleted
+	 *		<LI> importedKeySetDefault - change imported key to the default values if its primary key has been deleted
 	 *		</UL>
 	 *	<LI><B>FK_NAME</B> String =&gt; foreign key identifier (may be null)
 	 *	<LI><B>PK_NAME</B> String =&gt; primary key identifier (may be null)
@@ -2804,26 +2808,21 @@ public class MonetDatabaseMetaData
 	 *		being exported
 	 *	<LI><B>KEY_SEQ</B> short =&gt; sequence number within foreign key
 	 *		(a value of 1 represents the first column of the foreign key, a value of 2 would represent the second column within the foreign key).
-	 *	<LI><B>UPDATE_RULE</B> short =&gt; What happens to
-	 *		 foreign key when primary is updated:
+	 *	<LI><B>UPDATE_RULE</B> short =&gt; What happens to foreign key when primary is updated:
 	 *		<UL>
 	 *		<LI> importedKeyNoAction - do not allow update of primary key if it has been imported
-	 *		<LI> importedKeyCascade - change imported key to agree
-	 *				 with primary key update
-	 *		<LI> importedKeyRestrict - do not allow update of primary
-	 *				 key if it has been imported
-	 *		<LI> importedKeySetNull - change imported key to NULL if
-	 *				 its primary key has been updated
+	 *		<LI> importedKeyCascade - change imported key to agree with primary key update
+	 *		<LI> importedKeyRestrict - same as importedKeyNoAction (for ODBC 2.x compatibility)
+	 *		<LI> importedKeySetNull - change imported key to NULL if its primary key has been updated
+	 *		<LI> importedKeySetDefault - change imported key to the default values if its primary key has been updated
 	 *		</UL>
-	 *	<LI><B>DELETE_RULE</B> short =&gt; What happens to
-	 *		the foreign key when primary is deleted.
+	 *	<LI><B>DELETE_RULE</B> short =&gt; What happens to the foreign key when primary is deleted.
 	 *		<UL>
 	 *		<LI> importedKeyNoAction - do not allow delete of primary key if it has been imported
 	 *		<LI> importedKeyCascade - delete rows that import a deleted key
-	 *		<LI> importedKeyRestrict - do not allow delete of primary
-	 *				 key if it has been imported
-	 *		<LI> importedKeySetNull - change imported key to NULL if
-	 *				 its primary key has been deleted
+	 *		<LI> importedKeyRestrict - same as importedKeyNoAction (for ODBC 2.x compatibility)
+	 *		<LI> importedKeySetNull - change imported key to NULL if its primary key has been deleted
+	 *		<LI> importedKeySetDefault - change imported key to the default values if its primary key has been deleted
 	 *		</UL>
 	 *	<LI><B>FK_NAME</B> String =&gt; foreign key identifier (may be null)
 	 *	<LI><B>PK_NAME</B> String =&gt; primary key identifier (may be null)
--- a/src/main/java/org/monetdb/jdbc/MonetDriver.java.in
+++ b/src/main/java/org/monetdb/jdbc/MonetDriver.java.in
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.jdbc;
--- a/src/main/java/org/monetdb/jdbc/MonetPreparedStatement.java
+++ b/src/main/java/org/monetdb/jdbc/MonetPreparedStatement.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.jdbc;
--- a/src/main/java/org/monetdb/jdbc/MonetResultSet.java
+++ b/src/main/java/org/monetdb/jdbc/MonetResultSet.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.jdbc;
--- a/src/main/java/org/monetdb/jdbc/MonetSavepoint.java
+++ b/src/main/java/org/monetdb/jdbc/MonetSavepoint.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.jdbc;
--- a/src/main/java/org/monetdb/jdbc/MonetStatement.java
+++ b/src/main/java/org/monetdb/jdbc/MonetStatement.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.jdbc;
@@ -62,11 +62,11 @@ public class MonetStatement
 	/** Whether this Statement should be closed if the last ResultSet closes */
 	private boolean closeOnCompletion = false;
 	/** The timeout (in sec) for the query to return, 0 means no timeout */
-	private int queryTimeout = 0;
+	private int queryTimeout;
 	/** The size of the blocks of results to ask for at the server */
-	private int fetchSize = 0;
+	private int fetchSize;
 	/** The maximum number of rows to return in a ResultSet, 0 indicates unlimited */
-	private long maxRows = 0;
+	private long maxRows;
 	/** The type of ResultSet to produce; i.e. forward only, random access */
 	private int resultSetType = MonetResultSet.DEF_RESULTSETTYPE;
 	/** The suggested direction of fetching data (implemented but not used) */
@@ -75,8 +75,8 @@ public class MonetStatement
 	private int resultSetConcurrency = MonetResultSet.DEF_CONCURRENCY;
 
 	/** A List to hold all queries of a batch */
-	private ArrayList<String> batch = null;
-	private ReentrantLock batchLock = null;
+	private ArrayList<String> batch;
+	private ReentrantLock batchLock;
 
 
 	/**
@@ -164,17 +164,22 @@ public class MonetStatement
 	 * <li>A number greater than or equal to zero -- indicates that the
 	 * command was processed successfully and is an update count giving
 	 * the number of rows in the database that were affected by the
-	 * command's execution</li>
+	 * command's execution
 	 * <li>A value of SUCCESS_NO_INFO -- indicates that the command was
-	 * processed successfully but that the number of rows affected is
-	 * unknown</li>
+	 * processed successfully but that the number of rows affected is unknown
+	 * <p>If one of the commands in a batch update fails to execute properly,
+	 * this method throws a BatchUpdateException, and a JDBC driver may or
+	 * may not continue to process the remaining commands in the batch.
+	 * However, the driver's behavior must be consistent with a particular DBMS,
+	 * either always continuing to process commands or never continuing to process
+	 * commands. If the driver continues processing after a failure, the array
+	 * returned by the method BatchUpdateException.getUpdateCounts will
+	 * contain as many elements as there are commands in the batch, and at
+	 * least one of the elements will be the following:</p>
+	 * <li>A value of EXECUTE_FAILED -- indicates that the command failed to
+	 * execute successfully and occurs only if a driver continues to process
+	 * commands after a command fails
 	 * </ol>
-	 * If one of the commands in a batch update fails to execute
-	 * properly, this method throws a BatchUpdateException, and a JDBC
-	 * driver may or may not continue to process the remaining commands
-	 * in the batch.  However, the driver's behavior must be consistent
-	 * with a particular DBMS, either always continuing to process
-	 * commands or never continuing to process commands.
 	 *
 	 * MonetDB does continues after an error has occurred in the batch.
 	 * If one of the commands attempts to return a result set, an
@@ -184,11 +189,10 @@ public class MonetStatement
 	 *
 	 * @return an array of update counts containing one element for each
 	 *         command in the batch.  The elements of the array are ordered
-	 *         according to the order in which commands were added to the
-	 *         batch.
-	 * @throws SQLException if a database access error occurs.  Throws
-	 *         BatchUpdateException (a subclass of SQLException) if one of the
-	 *         commands sent to the database fails to execute properly
+	 *         according to the order in which commands were added to the batch.
+	 * @throws SQLException if a database access error occurs.
+	 * @throws BatchUpdateException (a subclass of SQLException) if one of the
+	 *         commands sent to the database fails to execute properly.
 	 */
 	@Override
 	public int[] executeBatch() throws SQLException {
@@ -196,103 +200,13 @@ public class MonetStatement
 			return new int[0];
 		}
 
-		// this method is synchronized/locked to make sure no one gets in between the
-		// operations we execute below
-		if (batchLock == null) {
-			// create a ReentrantLock at first time use
-			batchLock = new ReentrantLock();
+		final long[] ret = executeLargeBatch();
+		// copy contents of long[] into new int[]
+		final int[] counts = new int[ret.length];
+		for (int i = 0; i < ret.length; i++) {
+			counts[i] = (ret[i] >= Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)ret[i];
 		}
-		batchLock.lock();
-		try {
-			final int[] counts = new int[batch.size()];
-			final String sep = connection.queryTempl[2];
-			final int sepLen = sep.length();
-			final BatchUpdateException e = new BatchUpdateException("Error(s) occurred while executing the batch, see next SQLExceptions for details", "22000", counts);
-			final StringBuilder tmpBatch = new StringBuilder(MapiSocket.BLOCK);
-			int offset = 0;
-			boolean first = true;
-			boolean error = false;
-
-			for (int i = 0; i < batch.size(); i++) {
-				String tmp = batch.get(i);
-				if (sepLen + tmp.length() > MapiSocket.BLOCK) {
-					// The thing is too big.  Way too big.  Since it won't
-					// be optimal anyway, just add it to whatever we have
-					// and continue.
-					if (!first)
-						tmpBatch.append(sep);
-					tmpBatch.append(tmp);
-					// send and receive
-					error |= internalBatch(tmpBatch, counts, offset, i + 1, e);
-					offset = i;
-					tmpBatch.setLength(0);	// clear the buffer
-					first = true;
-					continue;
-				}
-				if (tmpBatch.length() + sepLen + tmp.length() >= MapiSocket.BLOCK) {
-					// send and receive
-					error |= internalBatch(tmpBatch, counts, offset, i + 1, e);
-					offset = i;
-					tmpBatch.setLength(0);	// clear the buffer
-					first = true;
-				}
-				if (first)
-					first = false;
-				else
-					tmpBatch.append(sep);
-				tmpBatch.append(tmp);
-			}
-			// send and receive
-			error |= internalBatch(tmpBatch, counts, offset, counts.length, e);
-
-			// throw BatchUpdateException if it contains something
-			if (error)
-				throw e;
-			// otherwise just return the counts
-			return counts;
-		} finally {
-			batch.clear();
-			batchLock.unlock();
-		}
-	}
-
-	private boolean internalBatch(
-			final StringBuilder batch,
-			final int[] counts,
-			int offset,
-			final int max,
-			final BatchUpdateException e)
-		throws BatchUpdateException
-	{
-		try {
-			boolean hasResultSet = internalExecute(batch.toString());
-			int count = -1;
-
-			if (!hasResultSet)
-				count = getUpdateCount();
-
-			do {
-				if (offset >= max)
-					throw new SQLException("Overflow: don't use multi statements when batching (" + max + ")", "M1M16");
-				if (hasResultSet) {
-					e.setNextException(
-						new SQLException("Batch query produced a ResultSet! " +
-							"Ignoring and setting update count to " +
-							"value " + EXECUTE_FAILED, "M1M17"));
-					counts[offset] = EXECUTE_FAILED;
-				} else if (count >= 0) {
-					counts[offset] = count;
-				}
-				offset++;
-			} while ((hasResultSet = getMoreResults()) || (count = getUpdateCount()) != -1);
-		} catch (SQLException ex) {
-			e.setNextException(ex);
-			for (; offset < max; offset++) {
-				counts[offset] = EXECUTE_FAILED;
-			}
-			return true;
-		}
-		return false;
+		return counts;
 	}
 
 	/**
@@ -517,23 +431,7 @@ public class MonetStatement
 
 		if (queryTimeout != connection.lastSetQueryTimeout) {
 			// set requested/changed queryTimeout on the server side first
-			Statement st = null;
-			try {
-				st = connection.createStatement();
-				final String callstmt = "CALL \"sys\".\"settimeout\"(" + queryTimeout + ")";
-				// for debug: System.out.println("Before: " + callstmt);
-				st.execute(callstmt);
-				// for debug: System.out.println("After : " + callstmt);
-				connection.lastSetQueryTimeout = queryTimeout;
-			}
-			/* do not catch SQLException here, as we want to know it when it fails */
-			finally {
-				if (st != null) {
-					try {
-						 st.close();
-					} catch (SQLException e) { /* ignore */ }
-				}
-			}
+			connection.setQueryTimeout(queryTimeout);
 		}
 
 		// create a container for the result
@@ -1273,10 +1171,10 @@ public class MonetStatement
 	 * <ol>
 	 * <li>A number greater than or equal to zero -- indicates that the command
 	 * was processed successfully and is an update count giving the number of
-	 * rows in the database that were affected by the command's execution</li>
+	 * rows in the database that were affected by the command's execution
 	 * <li>A value of SUCCESS_NO_INFO -- indicates that the command was
-	 * processed successfully but that the number of rows affected is unknown</li>
-	 * If one of the commands in a batch update fails to execute properly,
+	 * processed successfully but that the number of rows affected is unknown
+	 * <p>If one of the commands in a batch update fails to execute properly,
 	 * this method throws a BatchUpdateException, and a JDBC driver may or
 	 * may not continue to process the remaining commands in the batch.
 	 * However, the driver's behavior must be consistent with a particular DBMS,
@@ -1284,10 +1182,10 @@ public class MonetStatement
 	 * commands. If the driver continues processing after a failure, the array
 	 * returned by the method BatchUpdateException.getLargeUpdateCounts will
 	 * contain as many elements as there are commands in the batch, and at
-	 * least one of the elements will be the following:
+	 * least one of the elements will be the following:</p>
 	 * <li>A value of EXECUTE_FAILED -- indicates that the command failed to
 	 * execute successfully and occurs only if a driver continues to process
-	 * commands after a command fails</li>
+	 * commands after a command fails
 	 * </ol>
 	 *
 	 * This method should be used when the returned row count may exceed Integer.MAX_VALUE.
@@ -1304,8 +1202,9 @@ public class MonetStatement
 	 *	according to the order in which commands were added to the batch.
 	 * @throws SQLException if a database access error occurs, this method is called
 	 *	on a closed Statement or the driver does not support batch statements.
-	 *	Throws BatchUpdateException (a subclass of SQLException) if one of the
-	 *	commands sent to the database fails to execute properly or attempts to return a result set.
+	 * @throws BatchUpdateException (a subclass of SQLException) if one of the
+	 *	commands sent to the database fails to execute properly or
+	 *	attempts to return a result set.
 	 */
 	@Override
 	public long[] executeLargeBatch() throws SQLException {
@@ -1313,15 +1212,108 @@ public class MonetStatement
 			return new long[0];
 		}
 
-		final int[] ret = executeBatch();
-		// copy contents of int[] into new long[]
-		final long[] counts = new long[ret.length];
-		for (int i = 0; i < ret.length; i++) {
-			counts[i] = ret[i];
+		// this method is synchronized/locked to make sure no one gets in between the
+		// operations we execute below
+		if (batchLock == null) {
+			// create a ReentrantLock at first time use
+			batchLock = new ReentrantLock();
 		}
-		return counts;
+		batchLock.lock();
+		try {
+			final long[] counts = new long[batch.size()];
+			final String sep = connection.queryTempl[2];
+			final int sepLen = sep.length();
+			final BatchUpdateException e = new BatchUpdateException(
+					"Error(s) occurred while executing the batch, " +
+					"see chained SQLExceptions for details", "22000", 22000, counts, null);
+			final StringBuilder tmpBatch = new StringBuilder(MapiSocket.BLOCK);
+			int offset = 0;
+			boolean first = true;
+			boolean error = false;
+
+			for (int i = 0; i < batch.size(); i++) {
+				String tmp = batch.get(i);
+				if (sepLen + tmp.length() > MapiSocket.BLOCK) {
+					// The thing is too big.  Way too big.  Since it won't
+					// be optimal anyway, just add it to whatever we have
+					// and continue.
+					if (!first)
+						tmpBatch.append(sep);
+					tmpBatch.append(tmp);
+					// send and receive
+					error |= internalBatch(tmpBatch, counts, offset, i + 1, e);
+					offset = i;
+					tmpBatch.setLength(0);	// clear the buffer
+					first = true;
+					continue;
+				}
+				if (tmpBatch.length() + sepLen + tmp.length() >= MapiSocket.BLOCK) {
+					// send and receive
+					error |= internalBatch(tmpBatch, counts, offset, i + 1, e);
+					offset = i;
+					tmpBatch.setLength(0);	// clear the buffer
+					first = true;
+				}
+				if (first)
+					first = false;
+				else
+					tmpBatch.append(sep);
+				tmpBatch.append(tmp);
+			}
+			// send and receive
+			error |= internalBatch(tmpBatch, counts, offset, counts.length, e);
+
+			// throw BatchUpdateException if it contains something
+			if (error)
+				throw e;
+
+			// otherwise just return the counts
+			return counts;
+		} finally {
+			batch.clear();
+			batchLock.unlock();
+		}
 	}
 
+	private boolean internalBatch(
+			final StringBuilder batch,
+			final long[] counts,
+			int offset,
+			final int max,
+			final BatchUpdateException e)
+		throws BatchUpdateException
+	{
+		try {
+			long count = -1;
+			boolean hasResultSet = internalExecute(batch.toString());
+
+			if (!hasResultSet)
+				count = getLargeUpdateCount();
+
+			do {
+				if (offset >= max)
+					throw new SQLException("Overflow: don't use multi statements when batching (" + max + ")", "M1M16");
+				if (hasResultSet) {
+					e.setNextException(
+						new SQLException("Batch query produced a ResultSet! " +
+							"Ignoring and setting update count to value " + EXECUTE_FAILED, "M1M17"));
+					counts[offset] = EXECUTE_FAILED;
+				} else if (count >= 0) {
+					counts[offset] = count;
+				}
+				offset++;
+			} while ((hasResultSet = getMoreResults()) || (count = getLargeUpdateCount()) != -1);
+		} catch (SQLException ex) {
+			e.setNextException(ex);
+			for (; offset < max; offset++) {
+				counts[offset] = EXECUTE_FAILED;
+			}
+			return true;
+		}
+		return false;
+	}
+
+
 	/**
 	 * Executes the given SQL statement, which may be an INSERT, UPDATE, or
 	 * DELETE statement or an SQL statement that returns nothing, such as an
--- a/src/main/java/org/monetdb/jdbc/MonetWrapper.java
+++ b/src/main/java/org/monetdb/jdbc/MonetWrapper.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.jdbc;
--- a/src/main/java/org/monetdb/jdbc/types/INET.java
+++ b/src/main/java/org/monetdb/jdbc/types/INET.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.jdbc.types;
--- a/src/main/java/org/monetdb/jdbc/types/URL.java
+++ b/src/main/java/org/monetdb/jdbc/types/URL.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.jdbc.types;
--- a/src/main/java/org/monetdb/mcl/MCLException.java
+++ b/src/main/java/org/monetdb/mcl/MCLException.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.mcl;
--- a/src/main/java/org/monetdb/mcl/io/BufferedMCLReader.java
+++ b/src/main/java/org/monetdb/mcl/io/BufferedMCLReader.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.mcl.io;
--- a/src/main/java/org/monetdb/mcl/io/BufferedMCLWriter.java
+++ b/src/main/java/org/monetdb/mcl/io/BufferedMCLWriter.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.mcl.io;
--- a/src/main/java/org/monetdb/mcl/net/MapiSocket.java
+++ b/src/main/java/org/monetdb/mcl/net/MapiSocket.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.mcl.net;
--- a/src/main/java/org/monetdb/mcl/parser/HeaderLineParser.java
+++ b/src/main/java/org/monetdb/mcl/parser/HeaderLineParser.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.mcl.parser;
--- a/src/main/java/org/monetdb/mcl/parser/MCLParseException.java
+++ b/src/main/java/org/monetdb/mcl/parser/MCLParseException.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.mcl.parser;
--- a/src/main/java/org/monetdb/mcl/parser/MCLParser.java
+++ b/src/main/java/org/monetdb/mcl/parser/MCLParser.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.mcl.parser;
--- a/src/main/java/org/monetdb/mcl/parser/StartOfHeaderParser.java
+++ b/src/main/java/org/monetdb/mcl/parser/StartOfHeaderParser.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.mcl.parser;
--- a/src/main/java/org/monetdb/mcl/parser/TupleLineParser.java
+++ b/src/main/java/org/monetdb/mcl/parser/TupleLineParser.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.mcl.parser;
--- a/src/main/java/org/monetdb/merovingian/Control.java
+++ b/src/main/java/org/monetdb/merovingian/Control.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.merovingian;
--- a/src/main/java/org/monetdb/merovingian/MerovingianException.java
+++ b/src/main/java/org/monetdb/merovingian/MerovingianException.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.merovingian;
--- a/src/main/java/org/monetdb/merovingian/SabaothDB.java
+++ b/src/main/java/org/monetdb/merovingian/SabaothDB.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.merovingian;
--- a/src/main/java/org/monetdb/util/CmdLineOpts.java
+++ b/src/main/java/org/monetdb/util/CmdLineOpts.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.util;
--- a/src/main/java/org/monetdb/util/Exporter.java
+++ b/src/main/java/org/monetdb/util/Exporter.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.util;
--- a/src/main/java/org/monetdb/util/Extract.java
+++ b/src/main/java/org/monetdb/util/Extract.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.util;
--- a/src/main/java/org/monetdb/util/MDBvalidator.java
+++ b/src/main/java/org/monetdb/util/MDBvalidator.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.util;
@@ -17,7 +17,7 @@ import java.sql.SQLException;
 import java.sql.Types;
 
 /**
- * MonetDB Data Integrity Validator class (MDBvalidator) can
+ * <pre>MonetDB Data Integrity Validator class (MDBvalidator) can
  * a) validate system tables data integrity in system schemas: sys and tmp
  *    this includes violations of:
  *		primary key uniqueness
@@ -25,17 +25,17 @@ import java.sql.Types;
  *		unique constraint uniqueness
  *		foreign key referential integrity
  *		column not null
- *		column maximum length for char/varchar/clob/blob/json/url columns which have max length > 0
- * b) validate user schema tables & columns data integrity based on available meta data from system tables & system views
+ *		column maximum length for char/varchar/clob/blob/json/url columns which have max length &gt; 0
+ * b) validate user schema tables &amp; columns data integrity based on available meta data from system tables &amp; system views
  *		primary key uniqueness
  *	TODO primary key column(s) not null
  *		unique constraint uniqueness
  *	TODO foreign key referential integrity
  *		column not null
- *		column maximum length for char/varchar/clob/blob/json/url columns which have max length > 0
+ *		column maximum length for char/varchar/clob/blob/json/url columns which have max length &gt; 0
  *
  * More possible validations for future
- *		col char/varchar/clob/blob/json/url minimum length (some columns may not be empty, so length >= 1)
+ *		col char/varchar/clob/blob/json/url minimum length (some columns may not be empty, so length &gt;= 1)
  *		col with sequence (serial/bigserial/autoincrement) in range (0/1/min_value .. max_value)
  *		col value is valid in domain (date/time/timestamp/json/inet/url/uuid/...)
  *		col in list checks (some columns may have only certain values which are not stored in a table or view (eg as fk))
@@ -44,7 +44,7 @@ import java.sql.Types;
 		-- i.e.: either column_id or expression in sys.table_partitions must be populated
 		SELECT "column_id", "expression", 'Missing either column_id or expression' AS violation, * FROM "sys"."table_partitions" WHERE "column_id" IS NULL AND "expression" IS NULL;
 		SELECT "column_id", "expression", 'column_id and expression may not both be populated. One of them must be NULL' AS violation, * FROM "sys"."table_partitions" WHERE "column_id" IS NOT NULL AND "expression" IS NOT NULL;
- *
+ *</pre>
  * @author Martin van Dinther
  * @version 0.1
  */
--- a/src/main/java/org/monetdb/util/OptionsException.java
+++ b/src/main/java/org/monetdb/util/OptionsException.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.util;
--- a/src/main/java/org/monetdb/util/SQLExporter.java
+++ b/src/main/java/org/monetdb/util/SQLExporter.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.util;
--- a/src/main/java/org/monetdb/util/SQLRestore.java
+++ b/src/main/java/org/monetdb/util/SQLRestore.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.util;
--- a/src/main/java/org/monetdb/util/XMLExporter.java
+++ b/src/main/java/org/monetdb/util/XMLExporter.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 package org.monetdb.util;
--- a/tests/BugConcurrent_clients_SF_1504657.java
+++ b/tests/BugConcurrent_clients_SF_1504657.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/BugConcurrent_sequences.java
+++ b/tests/BugConcurrent_sequences.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/BugDatabaseMetaData_Bug_3356.java
+++ b/tests/BugDatabaseMetaData_Bug_3356.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/BugDecimalRound_Bug_3561.java
+++ b/tests/BugDecimalRound_Bug_3561.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/BugExecuteUpdate_Bug_3350.java
+++ b/tests/BugExecuteUpdate_Bug_3350.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/BugResultSetMetaData_Bug_6183.java
+++ b/tests/BugResultSetMetaData_Bug_6183.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/BugSetQueryTimeout_Bug_3357.java
+++ b/tests/BugSetQueryTimeout_Bug_3357.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Bug_Connect_as_voc_getMetaData_Failure_Bug_6388.java
+++ b/tests/Bug_Connect_as_voc_getMetaData_Failure_Bug_6388.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Bug_IsValid_Timeout_Bug_6782.java
+++ b/tests/Bug_IsValid_Timeout_Bug_6782.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Bug_LargeQueries_6571_6693.java
+++ b/tests/Bug_LargeQueries_6571_6693.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.Connection;
--- a/tests/Bug_PrepStmtSetObject_CLOB_6349.java
+++ b/tests/Bug_PrepStmtSetObject_CLOB_6349.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Bug_PrepStmtSetString_6382.java
+++ b/tests/Bug_PrepStmtSetString_6382.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Bug_PrepStmt_With_Errors_Jira292.java
+++ b/tests/Bug_PrepStmt_With_Errors_Jira292.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
new file mode 100644
--- /dev/null
+++ b/tests/JDBC_API_Tester.java
@@ -0,0 +1,3148 @@
+/*
+ * 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 - 2021 MonetDB B.V.
+ */
+
+import java.sql.*;
+
+import java.io.StringReader;
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TimeZone;
+
+import org.monetdb.jdbc.types.INET;
+import org.monetdb.jdbc.types.URL;
+
+/**
+ * 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
+	final static int sbInitLen = 3416;
+	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(sbInitLen);
+		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(con_URL);
+		jt.Test_PSmanycon(con_URL);
+		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: ").append(e).append("\n");
+			try {
+				// test calling conn.isValid()
+				sb.append("Validating connection: con.isValid? ").append(con.isValid(30));
+				// Can we rollback on this connection without causing an error?
+				con.rollback();
+			} catch (SQLException e2) {
+				sb.append("UnExpected error: ").append(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").append(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 ").append(size).append(" tuples, only got ").append(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").append(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 ").append(i).append(" 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 ").append(i).append(" 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 ").append(i).append(" 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").append(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: ").append(e.getMessage());
+			}
+			sb.append("\n");
+
+			con.setAutoCommit(false);
+			// >> true: auto commit should be on by default
+			sb.append("0. false\t").append(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 ").append(items).append(" items");
+			while (rs.next()) {
+				sb.append(", ").append(rs.getString("id"));
+				i++;
+			}
+			if (i != items) {
+				sb.append(" FAILED (").append(i).append(")");
+			}
+			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 ").append(items).append(" items");
+			while (rs.next()) {
+				sb.append(", ").append(rs.getString("id"));
+				i++;
+			}
+			if (i != items) {
+				sb.append(" FAILED (").append(i).append(")");
+			}
+			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 ").append(items).append(" items");
+			while (rs.next()) {
+				sb.append(", ").append(rs.getString("id"));
+				i++;
+			}
+			if (i != items) {
+				sb.append(" FAILED (").append(i).append(") :(");
+			}
+			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 ").append(items).append(" items");
+			while (rs.next()) {
+				sb.append(", ").append(rs.getString("id"));
+				i++;
+			}
+			if (i != items) {
+				sb.append(" FAILED (").append(i).append(") :(");
+			}
+			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: ").append(e.getMessage());
+		}
+		sb.append("\n");
+
+		try {
+			// turn off auto commit
+			con.setAutoCommit(false);
+			// >> false: we just disabled it
+			sb.append("2. false\t").append(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").append(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").append(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").append(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: ").append(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", "key_types", false, false), "getIndexInfo(null, sys, key_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	key_types	false	null	key_types_key_type_id_pkey	2	1	key_type_id	null	3	0	null\n" +
+			"null	sys	key_types	false	null	key_types_key_type_name_unique	2	1	key_type_name	null	3	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.setLength(0);	// clear the output log buffer
+			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++) {
+			if (col > 1)
+				sb.append("\t");
+			sb.append(rsmd.getColumnName(col));
+		}
+		sb.append("\n");
+		while (rs.next()) {
+			for (int col = 1; col <= columnCount; col++) {
+				if (col > 1)
+					sb.append("\t");
+				sb.append(rs.getString(col));
+			}
+			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: ").append(stmt.getFetchSize()).append("\n");
+			sb.append("ResultSet fetch size before set: ").append(rs.getFetchSize()).append("\n");
+
+			stmt.setFetchSize(40);
+			rs.setFetchSize(16384);
+
+			sb.append("Statement fetch size after set: ").append(stmt.getFetchSize()).append("\n");
+			sb.append("ResultSet fetch size after set: ").append(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: 40\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").append(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: ").append(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").append(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=").append(ti).append(" si=").append(si).append(" i=").append(i).append(" bi=").append(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 {
+			sb.append("4. Rollback changes...");
+			con.rollback();
+			sb.append(" passed").append("\n");
+
+			// 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" +
+			"4. Rollback changes... passed\n");
+	}
+
+	private void Test_PSlargebatchval() {
+		sb.setLength(0);	// clear the output log buffer
+
+		byte[] errorBytes = new byte[] { (byte) 0xe2, (byte) 0x80, (byte) 0xa7 };
+		String errorStr = new String(errorBytes, Charset.forName("UTF-8"));
+		StringBuilder repeatedErrorStr = new StringBuilder();
+		for (int i = 0; i < 8170;i++) {
+			repeatedErrorStr.append(errorStr);
+		}
+		String largeStr = repeatedErrorStr.toString();
+
+		Statement stmt = null;
+		PreparedStatement pstmt = null;
+		ResultSet rs = null;
+		try {
+			// >> true: auto commit should be on
+			sb.append("0. true\t").append(con.getAutoCommit()).append("\n");
+
+			stmt = con.createStatement();
+			sb.append("1. creating test table...");
+			stmt.execute("CREATE TABLE Test_PSlargebatchval (c INT, a CLOB, b DOUBLE)");
+			sb.append("success").append("\n");
+
+			sb.append("2. prepare insert...");
+			pstmt = con.prepareStatement("INSERT INTO Test_PSlargebatchval VALUES (?,?,?)");
+			sb.append("success").append("\n");
+
+			pstmt.setLong(1, 1L);
+			pstmt.setString(2, largeStr);
+			pstmt.setDouble(3, 1.0);
+			pstmt.addBatch();
+			pstmt.executeBatch();
+			sb.append("3. inserted 1 large string").append("\n");
+
+			/* test issue reported at https://www.monetdb.org/bugzilla/show_bug.cgi?id=3470 */
+			pstmt.setLong(1, -2L);
+			pstmt.setClob(2, new StringReader(largeStr));
+			pstmt.setDouble(3, -2.0);
+			pstmt.addBatch();
+			pstmt.executeBatch();
+			sb.append("4. inserted 1 large clob via StringReader() object").append("\n");
+
+			Clob myClob = con.createClob();
+			myClob.setString(1L, largeStr);
+
+			pstmt.setLong(1, 123456789L);
+			pstmt.setClob(2, myClob);
+			pstmt.setDouble(3, 12345678901.98765);
+			pstmt.addBatch();
+			pstmt.executeBatch();
+			sb.append("5. inserted 1 large clob via createClob() object").append("\n");
+
+			pstmt.close();
+
+			sb.append("6. select count(*)... ");
+			rs = stmt.executeQuery("SELECT COUNT(*) FROM Test_PSlargebatchval");
+			if (rs.next())
+				sb.append(rs.getInt(1)).append(" rows inserted.").append("\n");
+			rs.close();
+
+			sb.append("7. drop table...");
+			stmt.execute("DROP TABLE Test_PSlargebatchval");
+			sb.append("success").append("\n");
+			stmt.close();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+			while ((e = e.getNextException()) != null)
+				sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+		closeStmtResSet(stmt, rs);
+		closeStmtResSet(pstmt, null);
+
+		compareExpectedOutput("Test_PSlargebatchval",
+			"0. true	true\n" +
+			"1. creating test table...success\n" +
+			"2. prepare insert...success\n" +
+			"3. inserted 1 large string\n" +
+			"4. inserted 1 large clob via StringReader() object\n" +
+			"5. inserted 1 large clob via createClob() object\n" +
+			"6. select count(*)... 3 rows inserted.\n" +
+			"7. drop table...success\n");
+	}
+
+	private void Test_PSlargeresponse(String conURL) {
+		sb.setLength(0);	// clear the output log buffer
+
+		PreparedStatement pstmt = null;
+		try {
+			sb.append("1. DatabaseMetadata environment retrieval... ");
+
+			// retrieve this to simulate a bug report
+			DatabaseMetaData dbmd = con.getMetaData();
+			if (conURL.startsWith(dbmd.getURL()))
+				sb.append("oke");
+			else
+				sb.append("not oke ").append(dbmd.getURL());
+			sb.append("\n");
+
+			pstmt = con.prepareStatement("select * from columns");
+			sb.append("2. empty call...");
+			// should succeed (no arguments given)
+			pstmt.execute();
+			sb.append(" passed").append("\n");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+		closeStmtResSet(pstmt, null);
+
+		compareExpectedOutput("Test_PSlargeresponse",
+			"1. DatabaseMetadata environment retrieval... oke\n" +
+			"2. empty call... passed\n");
+	}
+
+	private void Test_PSmanycon(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<PreparedStatement> pss = new ArrayList<PreparedStatement>(maxCons);	// PreparedStatements go in here
+
+		try {
+			// spawn a lot of Connections with 1 PreparedStatement, just for fun...
+			int i = 1;
+			sb.append("Establishing Connection ");
+			for (; i <= maxCons; i++) {
+				sb.append(i);
+				Connection conx = DriverManager.getConnection(arg0);
+				sb.append(",");
+
+				// do something with the connection to test if it works
+				PreparedStatement pstmt = conx.prepareStatement("select " + i);
+				sb.append(" ");
+
+				pss.add(pstmt);
+			}
+			sb.append("\n");
+
+			// now try to nicely execute them
+			i = 1;
+			sb.append("Executing PreparedStatement\n");
+			for (Iterator<PreparedStatement> it = pss.iterator(); it.hasNext(); i++) {
+				PreparedStatement pstmt = it.next();
+
+				// see if the connection still works
+				sb.append(i).append("...");
+				if (!pstmt.execute())
+					sb.append("should have seen a ResultSet!");
+
+				ResultSet rs = pstmt.getResultSet();
+				if (!rs.next())
+					sb.append("ResultSet is empty");
+				sb.append(" result: ").append(rs.getString(1));
+
+				// close the connection and associated resources
+				pstmt.getConnection().close();
+				sb.append(", closed. ");
+
+				if (i % 5 == 0) {
+					// inject a failed transaction
+					Connection conZZ = DriverManager.getConnection(arg0);
+					Statement stmt = con.createStatement();
+					try {
+						int affrows = stmt.executeUpdate("update foo where bar is wrong");
+						sb.append("oops, faulty statement just got through");
+					} catch (SQLException e) {
+						sb.append("Forced transaction failure");
+					}
+					sb.append("\n");
+					closeStmtResSet(stmt, null);
+					conZZ.close();
+				}
+			}
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		compareExpectedOutput("Test_PSmanycon",
+			"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" +
+			"Executing PreparedStatement\n" +
+			"1... result: 1, closed. 2... result: 2, closed. 3... result: 3, closed. 4... result: 4, closed. 5... result: 5, closed. Forced transaction failure\n" +
+			"6... result: 6, closed. 7... result: 7, closed. 8... result: 8, closed. 9... result: 9, closed. 10... result: 10, closed. Forced transaction failure\n" +
+			"11... result: 11, closed. 12... result: 12, closed. 13... result: 13, closed. 14... result: 14, closed. 15... result: 15, closed. Forced transaction failure\n" +
+			"16... result: 16, closed. 17... result: 17, closed. 18... result: 18, closed. 19... result: 19, closed. 20... result: 20, closed. Forced transaction failure\n" +
+			"21... result: 21, closed. 22... result: 22, closed. 23... result: 23, closed. 24... result: 24, closed. 25... result: 25, closed. Forced transaction failure\n" +
+			"26... result: 26, closed. 27... result: 27, closed. 28... result: 28, closed. 29... result: 29, closed. 30... result: 30, closed. Forced transaction failure\n" +
+			"31... result: 31, closed. 32... result: 32, closed. 33... result: 33, closed. 34... result: 34, closed. 35... result: 35, closed. Forced transaction failure\n" +
+			"36... result: 36, closed. 37... result: 37, closed. 38... result: 38, closed. 39... result: 39, closed. 40... result: 40, closed. Forced transaction failure\n" +
+			"41... result: 41, closed. 42... result: 42, closed. 43... result: 43, closed. 44... result: 44, closed. 45... result: 45, closed. Forced transaction failure\n" +
+			"46... result: 46, closed. 47... result: 47, closed. 48... result: 48, closed. 49... result: 49, closed. 50... result: 50, closed. Forced transaction failure\n" +
+			"51... result: 51, closed. 52... result: 52, closed. 53... result: 53, closed. 54... result: 54, closed. 55... result: 55, closed. Forced transaction failure\n" +
+			"56... result: 56, closed. 57... result: 57, closed. 58... result: 58, closed. 59... result: 59, closed. 60... result: 60, closed. Forced transaction failure\n");
+	}
+
+	private void Test_PSmetadata() {
+		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").append(con.getAutoCommit()).append("\n");
+
+			stmt = con.createStatement();
+			int updates = 0;
+			updates = stmt.executeUpdate("CREATE TABLE table_Test_PSmetadata ( myint int, mydouble double, mybool boolean, myvarchar varchar(15), myclob clob )");
+			if (updates != Statement.SUCCESS_NO_INFO)
+				sb.append("1. Expected -2 got ").append(updates).append(" instead\n");
+
+			// all NULLs
+			updates = stmt.executeUpdate("INSERT INTO table_Test_PSmetadata VALUES (NULL, NULL,            NULL,           NULL,                  NULL)");
+			if (updates != 1)
+				sb.append("2a. Expected 1 got ").append(updates).append(" instead\n");
+
+			// all filled in
+			updates = stmt.executeUpdate("INSERT INTO table_Test_PSmetadata VALUES (2   , 3.0,             true,           'A string',            'bla bla bla')");
+			if (updates != 1)
+				sb.append("2b. Expected 1 got ").append(updates).append(" instead\n");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+		closeStmtResSet(stmt, null);
+
+		PreparedStatement pstmt = null;
+		try {
+			pstmt = con.prepareStatement("SELECT CASE WHEN myint IS NULL THEN 0 ELSE 1 END AS intnull, * FROM table_Test_PSmetadata WHERE myint = ?");
+
+			// testing and showing result set meta data
+			ResultSetMetaData rsmd = pstmt.getMetaData();
+			sb.append("rsmd. ").append(rsmd.getColumnCount()).append(" columns:\n");
+			for (int col = 1; col <= rsmd.getColumnCount(); col++) {
+				sb.append("RCol ").append(col).append("\n");
+				sb.append("  classname     ").append(rsmd.getColumnClassName(col)).append("\n");
+				sb.append("  displaysize   ").append(rsmd.getColumnDisplaySize(col)).append("\n");
+				sb.append("  label         ").append(rsmd.getColumnLabel(col)).append("\n");
+				sb.append("  name          ").append(rsmd.getColumnName(col)).append("\n");
+				sb.append("  type          ").append(rsmd.getColumnType(col)).append("\n");
+				sb.append("  typename      ").append(rsmd.getColumnTypeName(col)).append("\n");
+				sb.append("  precision     ").append(rsmd.getPrecision(col)).append("\n");
+				sb.append("  scale         ").append(rsmd.getScale(col)).append("\n");
+				sb.append("  catalogname   ").append(rsmd.getCatalogName(col)).append("\n");
+				sb.append("  schemaname    ").append(rsmd.getSchemaName(col)).append("\n");
+				sb.append("  tablename     ").append(rsmd.getTableName(col)).append("\n");
+				sb.append("  autoincrement ").append(rsmd.isAutoIncrement(col)).append("\n");
+				sb.append("  casesensitive ").append(rsmd.isCaseSensitive(col)).append("\n");
+				sb.append("  currency      ").append(rsmd.isCurrency(col)).append("\n");
+				sb.append("  defwritable   ").append(rsmd.isDefinitelyWritable(col)).append("\n");
+				sb.append("  nullable      ").append(rsmd.isNullable(col)).append("\n");
+				sb.append("  readonly      ").append(rsmd.isReadOnly(col)).append("\n");
+				sb.append("  searchable    ").append(rsmd.isSearchable(col)).append("\n");
+				sb.append("  signed        ").append(rsmd.isSigned(col)).append("\n");
+				sb.append("  writable      ").append(rsmd.isWritable(col)).append("\n");
+			}
+
+			showParams(pstmt);
+
+			con.rollback();
+			con.setAutoCommit(true);
+			// >> true: auto commit was just switched on
+			sb.append("0. true\t").append(con.getAutoCommit()).append("\n");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+		closeStmtResSet(pstmt, null);
+
+		compareExpectedOutput("Test_PSmetadata",
+			"0. false\tfalse\n" +
+			"rsmd. 6 columns:\n" +
+			"RCol 1\n" +
+			"  classname     java.lang.Short\n" +
+			"  displaysize   8\n" +
+			"  label         intnull\n" +
+			"  name          intnull\n" +
+			"  type          -6\n" +
+			"  typename      tinyint\n" +
+			"  precision     8\n" +
+			"  scale         0\n" +
+			"  catalogname   null\n" +
+			"  schemaname    \n" +
+			"  tablename     \n" +
+			"  autoincrement false\n" +
+			"  casesensitive false\n" +
+			"  currency      false\n" +
+			"  defwritable   false\n" +
+			"  nullable      2\n" +
+			"  readonly      true\n" +
+			"  searchable    true\n" +
+			"  signed        true\n" +
+			"  writable      false\n" +
+			"RCol 2\n" +
+			"  classname     java.lang.Integer\n" +
+			"  displaysize   32\n" +
+			"  label         myint\n" +
+			"  name          myint\n" +
+			"  type          4\n" +
+			"  typename      int\n" +
+			"  precision     32\n" +
+			"  scale         0\n" +
+			"  catalogname   null\n" +
+			"  schemaname    \n" +
+			"  tablename     table_test_psmetadata\n" +
+			"  autoincrement false\n" +
+			"  casesensitive false\n" +
+			"  currency      false\n" +
+			"  defwritable   false\n" +
+			"  nullable      2\n" +
+			"  readonly      true\n" +
+			"  searchable    true\n" +
+			"  signed        true\n" +
+			"  writable      false\n" +
+			"RCol 3\n" +
+			"  classname     java.lang.Double\n" +
+			"  displaysize   53\n" +
+			"  label         mydouble\n" +
+			"  name          mydouble\n" +
+			"  type          8\n" +
+			"  typename      double\n" +
+			"  precision     53\n" +
+			"  scale         0\n" +
+			"  catalogname   null\n" +
+			"  schemaname    \n" +
+			"  tablename     table_test_psmetadata\n" +
+			"  autoincrement false\n" +
+			"  casesensitive false\n" +
+			"  currency      false\n" +
+			"  defwritable   false\n" +
+			"  nullable      2\n" +
+			"  readonly      true\n" +
+			"  searchable    true\n" +
+			"  signed        true\n" +
+			"  writable      false\n" +
+			"RCol 4\n" +
+			"  classname     java.lang.Boolean\n" +
+			"  displaysize   1\n" +
+			"  label         mybool\n" +
+			"  name          mybool\n" +
+			"  type          16\n" +
+			"  typename      boolean\n" +
+			"  precision     1\n" +
+			"  scale         0\n" +
+			"  catalogname   null\n" +
+			"  schemaname    \n" +
+			"  tablename     table_test_psmetadata\n" +
+			"  autoincrement false\n" +
+			"  casesensitive false\n" +
+			"  currency      false\n" +
+			"  defwritable   false\n" +
+			"  nullable      2\n" +
+			"  readonly      true\n" +
+			"  searchable    true\n" +
+			"  signed        false\n" +
+			"  writable      false\n" +
+			"RCol 5\n" +
+			"  classname     java.lang.String\n" +
+			"  displaysize   15\n" +
+			"  label         myvarchar\n" +
+			"  name          myvarchar\n" +
+			"  type          12\n" +
+			"  typename      varchar\n" +
+			"  precision     15\n" +
+			"  scale         0\n" +
+			"  catalogname   null\n" +
+			"  schemaname    \n" +
+			"  tablename     table_test_psmetadata\n" +
+			"  autoincrement false\n" +
+			"  casesensitive true\n" +
+			"  currency      false\n" +
+			"  defwritable   false\n" +
+			"  nullable      2\n" +
+			"  readonly      true\n" +
+			"  searchable    true\n" +
+			"  signed        false\n" +
+			"  writable      false\n" +
+			"RCol 6\n" +
+			"  classname     java.lang.String\n" +
+			"  displaysize   0\n" +
+			"  label         myclob\n" +
+			"  name          myclob\n" +
+			"  type          12\n" +
+			"  typename      clob\n" +
+			"  precision     0\n" +
+			"  scale         0\n" +
+			"  catalogname   null\n" +
+			"  schemaname    \n" +
+			"  tablename     table_test_psmetadata\n" +
+			"  autoincrement false\n" +
+			"  casesensitive true\n" +
+			"  currency      false\n" +
+			"  defwritable   false\n" +
+			"  nullable      2\n" +
+			"  readonly      true\n" +
+			"  searchable    true\n" +
+			"  signed        false\n" +
+			"  writable      false\n" +
+			"pmd. 1 parameters:\n" +
+			"Param 1\n" +
+			"  nullable  2 (UNKNOWN)\n" +
+			"  signed    true\n" +
+			"  precision 32\n" +
+			"  scale     0\n" +
+			"  type      4\n" +
+			"  typename  int\n" +
+			"  classname java.lang.Integer\n" +
+			"  mode      1 (IN)\n" +
+			"0. true\ttrue\n");
+	}
+
+	private void Test_PSsomeamount() {
+		sb.setLength(0);	// clear the output log buffer
+
+		PreparedStatement pstmt = null;
+		ResultSet rs = null;
+		try {
+			// >> true: auto commit should be on
+			sb.append("0. true\t").append(con.getAutoCommit()).append("\n");
+
+			sb.append("1. Preparing and executing a unique statement").append("\n");
+			for (int i = 0; i < 120; i++) {
+				pstmt = con.prepareStatement("select " + i + ", " + i + " = ?");
+				pstmt.setInt(1, i);
+				rs = pstmt.executeQuery();
+				if (rs.next() && i % 20 == 0) {
+					sb.append(rs.getInt(1)).append(", ").append(rs.getBoolean(2)).append("\n");
+				}
+				/* next call should cause resources on the server to be freed */
+				pstmt.close();
+			}
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(pstmt, rs);
+
+		compareExpectedOutput("Test_PSsomeamount",
+			"0. true	true\n" +
+			"1. Preparing and executing a unique statement\n" +
+			"0, true\n" +
+			"20, true\n" +
+			"40, true\n" +
+			"60, true\n" +
+			"80, true\n" +
+			"100, true\n");
+	}
+
+	private void Test_PSsqldata() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		PreparedStatement pstmt = null;
+		ResultSet rs = null;
+		try {
+			con.setAutoCommit(false);
+			// >> false: auto commit should be off now
+			sb.append("0. false\t").append(con.getAutoCommit()).append("\n");
+
+			stmt = con.createStatement();
+			int updates = stmt.executeUpdate("CREATE TABLE table_Test_PSsqldata ( myinet inet, myurl url )");
+			if (updates != Statement.SUCCESS_NO_INFO)
+				sb.append("1. Expected -2 got ").append(updates).append(" instead\n");
+
+			pstmt = con.prepareStatement("INSERT INTO table_Test_PSsqldata VALUES (?, ?)");
+			ParameterMetaData pmd = pstmt.getParameterMetaData();
+			sb.append(pmd.getParameterCount()).append(" parameters:\n");
+			for (int parm = 1; parm <= pmd.getParameterCount(); parm++) {
+				sb.append("Parm ").append(parm).append("\n");
+				sb.append("  type      ").append(pmd.getParameterType(parm)).append("\n");
+				sb.append("  typename  ").append(pmd.getParameterTypeName(parm)).append("\n");
+				sb.append("  classname ").append(pmd.getParameterClassName(parm)).append("\n");
+			}
+
+			INET tinet = new INET();
+			tinet.fromString("172.5.5.5/24");
+
+			URL turl = new URL();
+			try {
+				turl.fromString("http://www.monetdb.org/");
+			} catch (Exception e) {
+				sb.append("conversion failed: ").append(e.getMessage()).append("\n");
+			}
+			pstmt.setObject(1, tinet);
+			pstmt.setObject(2, turl);
+			// insert first record
+			pstmt.execute();
+
+			try {
+				tinet.setNetmaskBits(16);
+			} catch (Exception e) {
+				sb.append("setNetmaskBits failed: ").append(e.getMessage()).append("\n");
+			}
+			// insert second record
+			pstmt.execute();
+
+			rs = stmt.executeQuery("SELECT * FROM table_Test_PSsqldata");
+			ResultSetMetaData rsmd = rs.getMetaData();
+			for (int i = 1; rs.next(); i++) {
+				for (int col = 1; col <= rsmd.getColumnCount(); col++) {
+					Object x = rs.getObject(col);
+					if (x == null || rs.wasNull()) {
+						sb.append(i).append(".\t<null>").append("\n");
+					} else {
+						sb.append(i).append(".\t").append(x.toString()).append("\n");
+						if (x instanceof INET) {
+							INET inet = (INET)x;
+							sb.append("  ").append(inet.getAddress()).append("/").append(inet.getNetmaskBits()).append("\n");
+							sb.append("  ").append(inet.getInetAddress().toString()).append("\n");
+						} else if (x instanceof URL) {
+							URL url = (URL)x;
+							sb.append("  ").append(url.getURL().toString()).append("\n");
+						}
+					}
+				}
+			}
+			con.rollback();
+			con.setAutoCommit(true);
+			// >> true: auto commit was just switched on
+			sb.append("0. true\t").append(con.getAutoCommit()).append("\n");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, rs);
+		closeStmtResSet(pstmt, null);
+
+		compareExpectedOutput("Test_PSsqldata",
+			"0. false	false\n" +
+			"2 parameters:\n" +
+			"Parm 1\n" +
+			"  type      12\n" +
+			"  typename  inet\n" +
+			"  classname org.monetdb.jdbc.types.INET\n" +
+			"Parm 2\n" +
+			"  type      12\n" +
+			"  typename  url\n" +
+			"  classname org.monetdb.jdbc.types.URL\n" +
+			"1.	172.5.5.5/24\n" +
+			"  172.5.5.5/24\n" +
+			"  /172.5.5.5\n" +
+			"1.	http://www.monetdb.org/\n" +
+			"  http://www.monetdb.org/\n" +
+			"2.	172.5.5.5/24\n" +
+			"  172.5.5.5/24\n" +
+			"  /172.5.5.5\n" +
+			"2.	http://www.monetdb.org/\n" +
+			"  http://www.monetdb.org/\n" +
+			"0. true	true\n");
+	}
+
+	private void Test_PStimedate() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		PreparedStatement pstmt = null;
+		ResultSet rs = null;
+		try {
+			con.setAutoCommit(false);
+			// >> false: auto commit should be off now
+			sb.append("0. false\t").append(con.getAutoCommit()).append("\n");
+
+			stmt = con.createStatement();
+			int updates = stmt.executeUpdate("CREATE TABLE Test_PStimedate (t time, ts timestamp, d date)");
+			if (updates != Statement.SUCCESS_NO_INFO)
+				sb.append("1. Expected -2 got ").append(updates).append(" instead\n");
+
+			pstmt = con.prepareStatement("INSERT INTO Test_PStimedate VALUES (?, ?, ?)");
+			sb.append("1. empty call...");
+			try {
+				// should fail (as no parameters set)
+				pstmt.execute();
+				sb.append(" UNexpected PASS!\n");
+			} catch (SQLException e) {
+				sb.append(" expected exception\n");
+			}
+
+			sb.append("2. inserting a record...");
+			java.util.Date d = new java.util.Date();
+			pstmt.setTime(1, new java.sql.Time(d.getTime()));
+			pstmt.setTimestamp(2, new java.sql.Timestamp(d.getTime()));
+			pstmt.setDate(3, new java.sql.Date(d.getTime()));
+
+			pstmt.executeUpdate();
+			sb.append(" passed\n");
+
+			sb.append("3. closing PreparedStatement...");
+			pstmt.close();
+			sb.append(" passed\n");
+
+			sb.append("4. selecting record...");
+			pstmt = con.prepareStatement("SELECT * FROM Test_PStimedate");
+			rs = pstmt.executeQuery();
+			sb.append(" passed\n");
+
+			while (rs.next()) {
+				for (int j = 1; j <= 3; j++) {
+					sb.append((j+4)).append(". retrieving...");
+					java.util.Date x = (java.util.Date)(rs.getObject(j));
+					boolean matches = false;
+					if (x instanceof Time) {
+						sb.append(" (Time)");
+						matches = (new Time(d.getTime())).toString().equals(x.toString());
+					} else if (x instanceof Date) {
+						sb.append(" (Date)");
+						matches = (new Date(d.getTime())).toString().equals(x.toString());
+					} else if (x instanceof Timestamp) {
+						sb.append(" (Timestamp)");
+						matches = (new Timestamp(d.getTime())).toString().equals(x.toString());
+					}
+					if (matches) {
+						sb.append(" passed\n");
+					} else {
+						sb.append(" FAILED (").append(x).append(" is not ").append(d).append(")\n");
+					}
+				}
+			}
+
+			con.rollback();
+			con.setAutoCommit(true);
+			// >> true: auto commit was just switched on
+			sb.append("0. true\t").append(con.getAutoCommit()).append("\n");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+		closeStmtResSet(pstmt, rs);
+
+		compareExpectedOutput("Test_PStimedate",
+			"0. false	false\n" +
+			"1. empty call... expected exception\n" +
+			"2. inserting a record... passed\n" +
+			"3. closing PreparedStatement... passed\n" +
+			"4. selecting record... passed\n" +
+			"5. retrieving... (Time) passed\n" +
+			"6. retrieving... (Timestamp) passed\n" +
+			"7. retrieving... (Date) passed\n" +
+			"0. true	true\n");
+	}
+
+	private void Test_PStimezone() {
+		sb.setLength(0);	// clear the output log buffer
+
+		// make sure this test is reproducable regardless timezone
+		// setting, by overriding the VM's default
+		// we have to make sure that one doesn't have daylight
+		// savings corrections
+		TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+
+		Statement stmt = null;
+		PreparedStatement pstmt = null;
+		ResultSet rs = null;
+		try {
+			con.setAutoCommit(false);
+			// >> false: auto commit should be off now
+			sb.append("0. false\t").append(con.getAutoCommit()).append("\n");
+
+			stmt = con.createStatement();
+			int updates = stmt.executeUpdate("CREATE TABLE Test_PStimezone (ts timestamp, tsz timestamp with time zone, t time, tz time with time zone)");
+			if (updates != Statement.SUCCESS_NO_INFO)
+				sb.append("1. Expected -2 got ").append(updates).append(" instead\n");
+
+			pstmt = con.prepareStatement("INSERT INTO Test_PStimezone VALUES (?, ?, ?, ?)");
+			sb.append("1. empty call...");
+			try {
+				// should fail (as no parameters set)
+				pstmt.execute();
+				sb.append(" UNexpected PASS!\n");
+			} catch (SQLException e) {
+				sb.append(" expected exception\n");
+			}
+
+			sb.append("2. inserting records...\n");
+			java.sql.Timestamp ts = new java.sql.Timestamp(0L);
+			java.sql.Time t = new java.sql.Time(0L);
+			Calendar c = Calendar.getInstance();
+			SimpleDateFormat tsz = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ");
+			SimpleDateFormat tz = new SimpleDateFormat("HH:mm:ss.SSSZ");
+
+			tsz.setTimeZone(c.getTimeZone());
+			tz.setTimeZone(tsz.getTimeZone());
+			sb.append("inserting (").append(c.getTimeZone().getID()).append(") ").append(tsz.format(ts)).append(", ").append(tz.format(t)).append("\n");
+
+			pstmt.setTimestamp(1, ts);
+			pstmt.setTimestamp(2, ts);
+			pstmt.setTime(3, t);
+			pstmt.setTime(4, t);
+			pstmt.executeUpdate();
+
+			c.setTimeZone(TimeZone.getTimeZone("UTC"));
+			sb.append("inserting with calendar timezone ").append(c.getTimeZone().getID()).append("\n");
+			pstmt.setTimestamp(1, ts, c);
+			pstmt.setTimestamp(2, ts, c);
+			pstmt.setTime(3, t, c);
+			pstmt.setTime(4, t, c);
+			pstmt.executeUpdate();
+
+			c.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
+			sb.append("inserting with calendar timezone ").append(c.getTimeZone().getID()).append("\n");
+			pstmt.setTimestamp(1, ts, c);
+			pstmt.setTimestamp(2, ts);
+			pstmt.setTime(3, t, c);
+			pstmt.setTime(4, t);
+			pstmt.executeUpdate();
+
+			c.setTimeZone(TimeZone.getTimeZone("GMT+04:15"));
+			sb.append("inserting with calendar timezone ").append(c.getTimeZone().getID()).append("\n");
+			pstmt.setTimestamp(1, ts);
+			pstmt.setTimestamp(2, ts, c);
+			pstmt.setTime(3, t);
+			pstmt.setTime(4, t, c);
+			pstmt.executeUpdate();
+			sb.append(" done\n");
+
+			sb.append("3. closing PreparedStatement...");
+			pstmt.close();
+			sb.append(" passed\n");
+
+			sb.append("4. selecting records...");
+			pstmt = con.prepareStatement("SELECT * FROM Test_PStimezone");
+			rs = pstmt.executeQuery();
+			sb.append(" passed\n");
+
+			// The tz fields should basically always be the same
+			// (exactly 1st Jan 1970) since whatever timezone is used,
+			// the server retains it, and Java restores it.
+			// The zoneless fields will show differences since the time
+			// is inserted translated to the given timezones, and
+			// retrieved as in they were given in those timezones.
+			//  When the insert zone matches the retrieve zone, Java should
+			// eventually see 1st Jan 1970.
+			while (rs.next()) {
+				sb.append("retrieved row (String):\n").append(
+						rs.getString("ts")).append(" | ").append(
+						rs.getString("tsz")).append(" | ").append(
+						rs.getString("t")).append(" | ").append(
+						rs.getString("tz")).append("\n");
+
+				tsz.setTimeZone(TimeZone.getDefault());
+				tz.setTimeZone(tsz.getTimeZone());
+				sb.append("default (").append(tsz.getTimeZone().getID()).append("):\n").append(
+						tsz.format(rs.getTimestamp("ts"))).append(" | ").append(
+						tsz.format(rs.getTimestamp("tsz"))).append(" | ").append(
+						tz.format(rs.getTime("t"))).append(" | ").append(
+						tz.format(rs.getTime("tz"))).append("\n");
+
+				c.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
+				sb.append(c.getTimeZone().getID()).append(":\n").append(
+						rs.getTimestamp("ts", c)).append(" | ").append(
+						rs.getTimestamp("tsz", c)).append(" | ").append(
+						rs.getTime("t", c)).append(" | ").append(
+						rs.getTime("tz", c)).append("\n");
+
+				c.setTimeZone(TimeZone.getTimeZone("Africa/Windhoek"));
+				sb.append(c.getTimeZone().getID()).append(":\n").append(
+						rs.getTimestamp("ts", c)).append(" | ").append(
+						rs.getTimestamp("tsz", c)).append(" | ").append(
+						rs.getTime("t", c)).append(" | ").append(
+						rs.getTime("tz", c)).append("\n");
+
+				SQLWarning w = rs.getWarnings();
+				while (w != null) {
+					sb.append(w.getMessage()).append("\n");
+					w = w.getNextWarning();
+				}
+			}
+
+			con.rollback();
+			con.setAutoCommit(true);
+			// >> true: auto commit was just switched on
+			sb.append("0. true\t").append(con.getAutoCommit()).append("\n");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+		closeStmtResSet(pstmt, rs);
+
+		compareExpectedOutput("Test_PStimezone",
+			"0. false	false\n" +
+			"1. empty call... expected exception\n" +
+			"2. inserting records...\n" +
+			"inserting (UTC) 1970-01-01 00:00:00.000+0000, 00:00:00.000+0000\n" +
+			"inserting with calendar timezone UTC\n" +
+			"inserting with calendar timezone America/Los_Angeles\n" +
+			"inserting with calendar timezone GMT+04:15\n" +
+			" done\n" +
+			"3. closing PreparedStatement... passed\n" +
+			"4. selecting records... passed\n" +
+			"retrieved row (String):\n" +
+			"1970-01-01 00:00:00.000000 | 1970-01-01 01:00:00.000000+01:00 | 00:00:00 | 01:00:00+01:00\n" +
+			"default (UTC):\n" +
+			"1970-01-01 00:00:00.000+0000 | 1970-01-01 00:00:00.000+0000 | 00:00:00.000+0000 | 00:00:00.000+0000\n" +
+			"America/Los_Angeles:\n" +
+			"1970-01-01 08:00:00.0 | 1970-01-01 00:00:00.0 | 08:00:00 | 00:00:00\n" +
+			"Africa/Windhoek:\n" +
+			"1969-12-31 22:00:00.0 | 1970-01-01 00:00:00.0 | 22:00:00 | 00:00:00\n" +
+			"retrieved row (String):\n" +
+			"1970-01-01 00:00:00.000000 | 1970-01-01 01:00:00.000000+01:00 | 00:00:00 | 01:00:00+01:00\n" +
+			"default (UTC):\n" +
+			"1970-01-01 00:00:00.000+0000 | 1970-01-01 00:00:00.000+0000 | 00:00:00.000+0000 | 00:00:00.000+0000\n" +
+			"America/Los_Angeles:\n" +
+			"1970-01-01 08:00:00.0 | 1970-01-01 00:00:00.0 | 08:00:00 | 00:00:00\n" +
+			"Africa/Windhoek:\n" +
+			"1969-12-31 22:00:00.0 | 1970-01-01 00:00:00.0 | 22:00:00 | 00:00:00\n" +
+			"retrieved row (String):\n" +
+			"1969-12-31 16:00:00.000000 | 1970-01-01 01:00:00.000000+01:00 | 16:00:00 | 01:00:00+01:00\n" +
+			"default (UTC):\n" +
+			"1969-12-31 16:00:00.000+0000 | 1970-01-01 00:00:00.000+0000 | 16:00:00.000+0000 | 00:00:00.000+0000\n" +
+			"America/Los_Angeles:\n" +
+			"1970-01-01 00:00:00.0 | 1970-01-01 00:00:00.0 | 00:00:00 | 00:00:00\n" +
+			"Africa/Windhoek:\n" +
+			"1969-12-31 14:00:00.0 | 1970-01-01 00:00:00.0 | 14:00:00 | 00:00:00\n" +
+			"retrieved row (String):\n" +
+			"1970-01-01 00:00:00.000000 | 1970-01-01 01:00:00.000000+01:00 | 00:00:00 | 01:00:00+01:00\n" +
+			"default (UTC):\n" +
+			"1970-01-01 00:00:00.000+0000 | 1970-01-01 00:00:00.000+0000 | 00:00:00.000+0000 | 00:00:00.000+0000\n" +
+			"America/Los_Angeles:\n" +
+			"1970-01-01 08:00:00.0 | 1970-01-01 00:00:00.0 | 08:00:00 | 00:00:00\n" +
+			"Africa/Windhoek:\n" +
+			"1969-12-31 22:00:00.0 | 1970-01-01 00:00:00.0 | 22:00:00 | 00:00:00\n" +
+			"0. true	true\n");
+	}
+
+	private void Test_PStypes() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		PreparedStatement pstmt = null;
+		try {
+			con.setAutoCommit(false);
+			// >> false: auto commit should be off now
+			sb.append("0. false\t").append(con.getAutoCommit()).append("\n");
+
+			stmt = con.createStatement();
+			int updates = stmt.executeUpdate(
+				"CREATE TABLE htmtest (" +
+				"       htmid    bigint       NOT NULL," +
+				"       ra       double ," +
+				"       decl     double ," +
+				"       dra      double ," +
+				"       ddecl    double ," +
+				"       flux     double ," +
+				"       dflux    double ," +
+				"       freq     double ," +
+				"       bw       double ," +
+				"       type     decimal(1,0)," +
+				"       imageurl varchar(100)," +
+				"       comment  varchar(100)," +
+				"       CONSTRAINT htmtest_htmid_pkey PRIMARY KEY (htmid)" +
+				")" );
+			if (updates != Statement.SUCCESS_NO_INFO)
+				sb.append("1. Expected -2 got ").append(updates).append(" instead\n");
+
+			// index is not used, but the original bug had it too
+			updates = stmt.executeUpdate("CREATE INDEX htmid ON htmtest (htmid)");
+			if (updates != Statement.SUCCESS_NO_INFO)
+				sb.append("1. Expected -2 got ").append(updates).append(" instead\n");
+
+			stmt.close();
+
+			pstmt = con.prepareStatement("INSERT INTO HTMTEST (HTMID,RA,DECL,FLUX,COMMENT) VALUES (?,?,?,?,?)");
+			sb.append("1. inserting a record...");
+			pstmt.setLong(1, 1L);
+			pstmt.setFloat(2, (float)1.2);
+			pstmt.setDouble(3, 2.4);
+			pstmt.setDouble(4, 3.2);
+			pstmt.setString(5, "vlavbla");
+			pstmt.executeUpdate();
+			sb.append("success\n");
+
+			// try an update like bug #1757923
+			pstmt = con.prepareStatement("UPDATE HTMTEST set COMMENT=?, TYPE=? WHERE HTMID=?");
+			sb.append("2. updating record...");
+			pstmt.setString(1, "some update");
+			pstmt.setObject(2, (float)3.2);
+			pstmt.setLong(3, 1L);
+			pstmt.executeUpdate();
+			sb.append("success\n");
+
+			pstmt.close();
+
+			con.rollback();
+			con.setAutoCommit(true);
+			// >> true: auto commit was just switched on
+			sb.append("0. true\t").append(con.getAutoCommit()).append("\n");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+		closeStmtResSet(pstmt, null);
+
+		compareExpectedOutput("Test_PStypes",
+			"0. false	false\n" +
+			"1. inserting a record...success\n" +
+			"2. updating record...success\n" +
+			"0. true	true\n");
+	}
+
+	private void Test_CallableStmt() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		CallableStatement cstmt = null;
+		try {
+			String tbl_nm = "tbl6402";
+			String proc_nm = "proc6402";
+
+			stmt = con.createStatement();
+
+			// create a test table.
+			stmt.executeUpdate("CREATE TABLE IF NOT EXISTS " + tbl_nm + " (tint int, tdouble double, tbool boolean, tvarchar varchar(15), tclob clob, turl url, tclen int);");
+			sb.append("Created table: ").append(tbl_nm).append("\n");
+
+			// create a procedure with multiple different IN parameters which inserts a row into a table of which one column is computed.
+			stmt.executeUpdate("CREATE PROCEDURE " + proc_nm + " (myint int, mydouble double, mybool boolean, myvarchar varchar(15), myclob clob, myurl url) BEGIN" +
+				" INSERT INTO " + tbl_nm + " (tint, tdouble, tbool, tvarchar, tclob, turl, tclen) VALUES (myint, mydouble, mybool, myvarchar, myclob, myurl, LENGTH(myvarchar) + LENGTH(myclob)); " +
+				"END;");
+			sb.append("Created procedure: ").append(proc_nm).append("\n");
+
+			// make sure we can call the procedure the old way (as string)
+			stmt.executeUpdate("call " + proc_nm + "(1, 1.1, true,'one','ONE', 'www.monetdb.org');");
+			sb.append("Called procedure (1): ").append(proc_nm).append("\n");
+			showTblContents(tbl_nm);
+
+			// now use a CallableStament object
+			cstmt = con.prepareCall(" { call " + proc_nm + " (?,?, ?, ? , ?,?) } ;");
+			sb.append("Prepared Callable procedure: ").append(proc_nm).append("\n");
+
+			// specify first set of params
+			cstmt.setInt(1, 2);
+			cstmt.setDouble(2, 2.02);
+			cstmt.setBoolean(3, true);
+			cstmt.setString(4, "Two");
+			Clob myclob = con.createClob();
+			myclob.setString(1, "TWOs");
+			cstmt.setClob(5, myclob);
+			cstmt.setString(6, "http://www.monetdb.org/");
+			cstmt.execute();
+			sb.append("Called Prepared procedure (1): ").append(proc_nm).append("\n");
+			showParams(cstmt);
+			showTblContents(tbl_nm);
+
+			myclob.setString(1, "TREEs");
+			// specify second set of params (some (1 and 3 and 5) are left the same)
+			cstmt.setDouble(2, 3.02);
+			cstmt.setString(4, "Tree");
+			try {
+				cstmt.setURL(6, new java.net.URL("https://www.monetdb.org/"));
+			} catch (java.net.MalformedURLException mfue) {
+				sb.append("Invalid URL: ").append(mfue.getMessage()).append("\n");
+			}
+			cstmt.execute();
+			sb.append("Called Prepared procedure (2): ").append(proc_nm).append("\n");
+			// showParams(cstmt);
+			showTblContents(tbl_nm);
+
+			// specify third set of params (some (1 and 2) are left the same)
+			cstmt.setInt(1, 4);
+			cstmt.setBoolean(3, false);
+			cstmt.setString(4, "Four");
+			cstmt.executeUpdate();
+			sb.append("Called Prepared procedure (3): ").append(proc_nm).append("\n");
+			showTblContents(tbl_nm);
+
+			// test setNull() also
+			cstmt.setNull(3, Types.BOOLEAN);
+			cstmt.setNull(5, Types.CLOB);
+			cstmt.setNull(2, Types.DOUBLE);
+			cstmt.setNull(4, Types.VARCHAR);
+			cstmt.setNull(1, Types.INTEGER);
+			cstmt.executeUpdate();
+			sb.append("Called Prepared procedure (with NULLs): ").append(proc_nm).append("\n");
+			showTblContents(tbl_nm);
+
+			sb.append("Test completed. Cleanup procedure and table.").append("\n");
+			stmt.execute("DROP PROCEDURE IF EXISTS " + proc_nm + ";");
+			stmt.execute("DROP TABLE     IF EXISTS " + tbl_nm + ";");
+
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, null);
+		closeStmtResSet(cstmt, null);
+
+		compareExpectedOutput("Test_CallableStmt",
+			"Created table: tbl6402\n" +
+			"Created procedure: proc6402\n" +
+			"Called procedure (1): proc6402\n" +
+			"Table tbl6402 has 7 columns:\n" +
+			"	tint	tdouble	tbool	tvarchar	tclob	turl	tclen\n" +
+			"	1	1.1	true	one	ONE	www.monetdb.org	6\n" +
+			"Prepared Callable procedure: proc6402\n" +
+			"Called Prepared procedure (1): proc6402\n" +
+			"pmd. 6 parameters:\n" +
+			"Param 1\n" +
+			"  nullable  2 (UNKNOWN)\n" +
+			"  signed    true\n" +
+			"  precision 32\n" +
+			"  scale     0\n" +
+			"  type      4\n" +
+			"  typename  int\n" +
+			"  classname java.lang.Integer\n" +
+			"  mode      1 (IN)\n" +
+			"Param 2\n" +
+			"  nullable  2 (UNKNOWN)\n" +
+			"  signed    true\n" +
+			"  precision 53\n" +
+			"  scale     0\n" +
+			"  type      8\n" +
+			"  typename  double\n" +
+			"  classname java.lang.Double\n" +
+			"  mode      1 (IN)\n" +
+			"Param 3\n" +
+			"  nullable  2 (UNKNOWN)\n" +
+			"  signed    false\n" +
+			"  precision 1\n" +
+			"  scale     0\n" +
+			"  type      16\n" +
+			"  typename  boolean\n" +
+			"  classname java.lang.Boolean\n" +
+			"  mode      1 (IN)\n" +
+			"Param 4\n" +
+			"  nullable  2 (UNKNOWN)\n" +
+			"  signed    false\n" +
+			"  precision 15\n" +
+			"  scale     0\n" +
+			"  type      12\n" +
+			"  typename  varchar\n" +
+			"  classname java.lang.String\n" +
+			"  mode      1 (IN)\n" +
+			"Param 5\n" +
+			"  nullable  2 (UNKNOWN)\n" +
+			"  signed    false\n" +
+			"  precision 0\n" +
+			"  scale     0\n" +
+			"  type      12\n" +
+			"  typename  clob\n" +
+			"  classname java.lang.String\n" +
+			"  mode      1 (IN)\n" +
+			"Param 6\n" +
+			"  nullable  2 (UNKNOWN)\n" +
+			"  signed    false\n" +
+			"  precision 0\n" +
+			"  scale     0\n" +
+			"  type      12\n" +
+			"  typename  url\n" +
+			"  classname org.monetdb.jdbc.types.URL\n" +
+			"  mode      1 (IN)\n" +
+			"Table tbl6402 has 7 columns:\n" +
+			"	tint	tdouble	tbool	tvarchar	tclob	turl	tclen\n" +
+			"	1	1.1	true	one	ONE	www.monetdb.org	6\n" +
+			"	2	2.02	true	Two	TWOs	http://www.monetdb.org/	7\n" +
+			"Called Prepared procedure (2): proc6402\n" +
+			"Table tbl6402 has 7 columns:\n" +
+			"	tint	tdouble	tbool	tvarchar	tclob	turl	tclen\n" +
+			"	1	1.1	true	one	ONE	www.monetdb.org	6\n" +
+			"	2	2.02	true	Two	TWOs	http://www.monetdb.org/	7\n" +
+			"	2	3.02	true	Tree	TWOs	https://www.monetdb.org/	8\n" +
+			"Called Prepared procedure (3): proc6402\n" +
+			"Table tbl6402 has 7 columns:\n" +
+			"	tint	tdouble	tbool	tvarchar	tclob	turl	tclen\n" +
+			"	1	1.1	true	one	ONE	www.monetdb.org	6\n" +
+			"	2	2.02	true	Two	TWOs	http://www.monetdb.org/	7\n" +
+			"	2	3.02	true	Tree	TWOs	https://www.monetdb.org/	8\n" +
+			"	4	3.02	false	Four	TWOs	https://www.monetdb.org/	8\n" +
+			"Called Prepared procedure (with NULLs): proc6402\n" +
+			"Table tbl6402 has 7 columns:\n" +
+			"	tint	tdouble	tbool	tvarchar	tclob	turl	tclen\n" +
+			"	1	1.1	true	one	ONE	www.monetdb.org	6\n" +
+			"	2	2.02	true	Two	TWOs	http://www.monetdb.org/	7\n" +
+			"	2	3.02	true	Tree	TWOs	https://www.monetdb.org/	8\n" +
+			"	4	3.02	false	Four	TWOs	https://www.monetdb.org/	8\n" +
+			"	null	null	null	null	null	https://www.monetdb.org/	null\n" +
+			"Test completed. Cleanup procedure and table.\n");
+	}
+
+	private void Test_Rbooleans() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		ResultSet rs = null;
+		try {
+			con.setAutoCommit(false);
+			// >> false: auto commit should be off now
+			sb.append("0. false\t").append(con.getAutoCommit()).append("\n");
+
+			stmt = con.createStatement();
+			int updates = stmt.executeUpdate(
+				"CREATE TABLE Test_Rbooleans (" +
+				" id int, tiny_int tinyint, small_int smallint, medium_int mediumint, \"integer\" int, big_int bigint," +
+				" a_real real, a_float float, a_double double, a_decimal decimal(8,2), a_numeric numeric(8)," +
+				" bool boolean, a_char char(4), b_char char(5), a_varchar varchar(20), PRIMARY KEY (id) )");
+			if (updates != Statement.SUCCESS_NO_INFO)
+				sb.append("1a. Expected -2 got ").append(updates).append(" instead\n");
+
+			// all falses
+			updates = stmt.executeUpdate("INSERT INTO Test_Rbooleans VALUES (1,0,0,0,0,0,0.0,0.0,0.0,0.0,0,false,'fals','false','false')");
+			// all trues
+			updates += stmt.executeUpdate("INSERT INTO Test_Rbooleans VALUES (2,1,1,1,1,1,1.0,1.0,1.0,1.0,1,true,'true','true ','true')");
+			// sneakier
+			updates += stmt.executeUpdate("INSERT INTO Test_Rbooleans VALUES (3,2,3,4,5,6,7.1,8.2,9.3,10.4,11,true,'TrUe','fAlSe','true/false')");
+			updates += stmt.executeUpdate("INSERT INTO Test_Rbooleans VALUES (4,2,3,4,5,6,7.1,8.2,9.3,10.4,11,true,'t   ','f    ','TRUE      ')");
+			if (updates != 4)
+				sb.append("1b. Expected 4 got ").append(updates).append(" instead\n");
+
+			rs = stmt.executeQuery("SELECT * FROM Test_Rbooleans ORDER BY id ASC");
+
+			// all should give false
+			rs.next();
+			sb.append("1. ").append(rs.getInt("id")).append(", ").append(rs.getBoolean("tiny_int"))
+			.append(", ").append(rs.getBoolean("small_int")).append(", ").append(rs.getBoolean("medium_int"))
+			.append(", ").append(rs.getBoolean("integer")).append(", ").append(rs.getBoolean("big_int"))
+			.append(", ").append(rs.getBoolean("a_real")).append(", ").append(rs.getBoolean("a_double"))
+			.append(", ").append(rs.getBoolean("a_decimal")).append(", ").append(rs.getBoolean("a_numeric"))
+			.append(", ").append(rs.getBoolean("bool")).append(", ").append(rs.getBoolean("a_char"))
+			.append(", ").append(rs.getBoolean("b_char")).append(", ").append(rs.getBoolean("a_varchar")).append("\n");
+
+			// all should give true except the one before last
+			rs.next();
+			sb.append("2. ").append(rs.getInt("id")).append(", ").append(rs.getBoolean("tiny_int"))
+			.append(", ").append(rs.getBoolean("small_int")).append(", ").append(rs.getBoolean("medium_int"))
+			.append(", ").append(rs.getBoolean("integer")).append(", ").append(rs.getBoolean("big_int"))
+			.append(", ").append(rs.getBoolean("a_real")).append(", ").append(rs.getBoolean("a_double"))
+			.append(", ").append(rs.getBoolean("a_decimal")).append(", ").append(rs.getBoolean("a_numeric"))
+			.append(", ").append(rs.getBoolean("bool")).append(", ").append(rs.getBoolean("a_char"))
+			.append(", ").append(rs.getBoolean("b_char")).append(", ").append(rs.getBoolean("a_varchar")).append("\n");
+
+			// should give true for all but the last two
+			rs.next();
+			sb.append("3. ").append(rs.getInt("id")).append(", ").append(rs.getBoolean("tiny_int"))
+			.append(", ").append(rs.getBoolean("small_int")).append(", ").append(rs.getBoolean("medium_int"))
+			.append(", ").append(rs.getBoolean("integer")).append(", ").append(rs.getBoolean("big_int"))
+			.append(", ").append(rs.getBoolean("a_real")).append(", ").append(rs.getBoolean("a_double"))
+			.append(", ").append(rs.getBoolean("a_decimal")).append(", ").append(rs.getBoolean("a_numeric"))
+			.append(", ").append(rs.getBoolean("bool")).append(", ").append(rs.getBoolean("a_char"))
+			.append(", ").append(rs.getBoolean("b_char")).append(", ").append(rs.getBoolean("a_varchar")).append("\n");
+
+			// should give true for all but the last three
+			rs.next();
+			sb.append("4. ").append(rs.getInt("id")).append(", ").append(rs.getBoolean("tiny_int"))
+			.append(", ").append(rs.getBoolean("small_int")).append(", ").append(rs.getBoolean("medium_int"))
+			.append(", ").append(rs.getBoolean("integer")).append(", ").append(rs.getBoolean("big_int"))
+			.append(", ").append(rs.getBoolean("a_real")).append(", ").append(rs.getBoolean("a_double"))
+			.append(", ").append(rs.getBoolean("a_decimal")).append(", ").append(rs.getBoolean("a_numeric"))
+			.append(", ").append(rs.getBoolean("bool")).append(", ").append(rs.getBoolean("a_char"))
+			.append(", ").append(rs.getBoolean("b_char")).append(", ").append(rs.getBoolean("a_varchar")).append("\n");
+			rs.next();
+
+			con.rollback();
+			con.setAutoCommit(true);
+			// >> true: auto commit was just switched on
+			sb.append("0. true\t").append(con.getAutoCommit()).append("\n");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, rs);
+
+		compareExpectedOutput("Test_Rbooleans",
+			"0. false	false\n" +
+			"1. 1, false, false, false, false, false, false, false, false, false, false, false, false, false\n" +
+			"2. 2, true, true, true, true, true, true, true, true, true, true, true, false, true\n" +
+			"3. 3, true, true, true, true, true, true, true, true, true, true, true, false, false\n" +
+			"4. 4, true, true, true, true, true, true, true, true, true, true, false, false, false\n" +
+			"0. true	true\n");
+	}
+
+	private void Test_Rmetadata() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		ResultSet rs = null;
+		try {
+			con.setAutoCommit(false);
+			// >> false: auto commit should be off now
+			sb.append("0. false\t").append(con.getAutoCommit()).append("\n");
+
+			stmt = con.createStatement();
+			stmt.executeUpdate("CREATE TABLE Test_Rmetadata ( myint int, mydouble double, mybool boolean, myvarchar varchar(15), myclob clob )");
+
+			// all NULLs
+			stmt.executeUpdate("INSERT INTO Test_Rmetadata VALUES (NULL, NULL, NULL, NULL, NULL)");
+			// all filled in
+			stmt.executeUpdate("INSERT INTO Test_Rmetadata VALUES (2 , 3.0, true, 'A string', 'bla bla bla')");
+
+			rs = stmt.executeQuery("SELECT * FROM Test_Rmetadata");
+
+			ResultSetMetaData rsmd = rs.getMetaData();
+			sb.append("0. ").append(rsmd.getColumnCount()).append(" columns:\n");
+			for (int col = 1; col <= rsmd.getColumnCount(); col++) {
+				sb.append("Colnr ").append(col).append(".\n");
+				sb.append("\tclassname     ").append(rsmd.getColumnClassName(col)).append("\n");
+				sb.append("\tdisplaysize   ").append(rsmd.getColumnDisplaySize(col)).append("\n");
+				sb.append("\tlabel         ").append(rsmd.getColumnLabel(col)).append("\n");
+				sb.append("\tname          ").append(rsmd.getColumnName(col)).append("\n");
+				sb.append("\ttype          ").append(rsmd.getColumnType(col)).append("\n");
+				sb.append("\ttypename      ").append(rsmd.getColumnTypeName(col)).append("\n");
+				sb.append("\tprecision     ").append(rsmd.getPrecision(col)).append("\n");
+				sb.append("\tscale         ").append(rsmd.getScale(col)).append("\n");
+				sb.append("\tcatalogname   ").append(rsmd.getCatalogName(col)).append("\n");
+				sb.append("\tschemaname    ").append(rsmd.getSchemaName(col)).append("\n");
+				sb.append("\ttablename     ").append(rsmd.getTableName(col)).append("\n");
+				sb.append("\tautoincrement ").append(rsmd.isAutoIncrement(col)).append("\n");
+				sb.append("\tcasesensitive ").append(rsmd.isCaseSensitive(col)).append("\n");
+				sb.append("\tcurrency      ").append(rsmd.isCurrency(col)).append("\n");
+				sb.append("\tdefwritable   ").append(rsmd.isDefinitelyWritable(col)).append("\n");
+				sb.append("\tnullable      ").append(rsmd.isNullable(col)).append("\n");
+				sb.append("\treadonly      ").append(rsmd.isReadOnly(col)).append("\n");
+				sb.append("\tsearchable    ").append(rsmd.isSearchable(col)).append("\n");
+				sb.append("\tsigned        ").append(rsmd.isSigned(col)).append("\n");
+				sb.append("\twritable      ").append(rsmd.isWritable(col)).append("\n");
+			}
+
+			for (int i = 6; rs.next(); i++) {
+				for (int col = 1; col <= rsmd.getColumnCount(); col++) {
+					Object obj = rs.getObject(col);
+					String type = rsmd.getColumnClassName(col);
+					String isInstance = "(null)";
+					if (obj != null && type != null) {
+						try {
+							Class<?> c = Class.forName(type);
+							if (c.isInstance(obj)) {
+								isInstance = (obj.getClass().getName() + " is an instance of " + type);
+							} else {
+								isInstance = (obj.getClass().getName() + " is NOT an instance of " + type);
+							}
+						} catch (ClassNotFoundException e) {
+							isInstance = "No such class: " + type;
+						}
+					}
+					sb.append(i).append(".\t").append(isInstance).append("\n");
+				}
+			}
+			rs.close();
+
+			con.rollback();
+			con.setAutoCommit(true);
+			// >> true: auto commit was just switched on
+			sb.append("0. true\t").append(con.getAutoCommit()).append("\n");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, rs);
+
+		compareExpectedOutput("Test_Rmetadata",
+			"0. false	false\n" +
+			"0. 5 columns:\n" +
+			"Colnr 1.\n" +
+			"	classname     java.lang.Integer\n" +
+			"	displaysize   1\n" +
+			"	label         myint\n" +
+			"	name          myint\n" +
+			"	type          4\n" +
+			"	typename      int\n" +
+			"	precision     10\n" +
+			"	scale         0\n" +
+			"	catalogname   null\n" +
+			"	schemaname    sys\n" +
+			"	tablename     test_rmetadata\n" +
+			"	autoincrement false\n" +
+			"	casesensitive false\n" +
+			"	currency      false\n" +
+			"	defwritable   false\n" +
+			"	nullable      1\n" +
+			"	readonly      true\n" +
+			"	searchable    true\n" +
+			"	signed        true\n" +
+			"	writable      false\n" +
+			"Colnr 2.\n" +
+			"	classname     java.lang.Double\n" +
+			"	displaysize   24\n" +
+			"	label         mydouble\n" +
+			"	name          mydouble\n" +
+			"	type          8\n" +
+			"	typename      double\n" +
+			"	precision     15\n" +
+			"	scale         0\n" +
+			"	catalogname   null\n" +
+			"	schemaname    sys\n" +
+			"	tablename     test_rmetadata\n" +
+			"	autoincrement false\n" +
+			"	casesensitive false\n" +
+			"	currency      false\n" +
+			"	defwritable   false\n" +
+			"	nullable      1\n" +
+			"	readonly      true\n" +
+			"	searchable    true\n" +
+			"	signed        true\n" +
+			"	writable      false\n" +
+			"Colnr 3.\n" +
+			"	classname     java.lang.Boolean\n" +
+			"	displaysize   5\n" +
+			"	label         mybool\n" +
+			"	name          mybool\n" +
+			"	type          16\n" +
+			"	typename      boolean\n" +
+			"	precision     1\n" +
+			"	scale         0\n" +
+			"	catalogname   null\n" +
+			"	schemaname    sys\n" +
+			"	tablename     test_rmetadata\n" +
+			"	autoincrement false\n" +
+			"	casesensitive false\n" +
+			"	currency      false\n" +
+			"	defwritable   false\n" +
+			"	nullable      1\n" +
+			"	readonly      true\n" +
+			"	searchable    true\n" +
+			"	signed        false\n" +
+			"	writable      false\n" +
+			"Colnr 4.\n" +
+			"	classname     java.lang.String\n" +
+			"	displaysize   8\n" +
+			"	label         myvarchar\n" +
+			"	name          myvarchar\n" +
+			"	type          12\n" +
+			"	typename      varchar\n" +
+			"	precision     15\n" +
+			"	scale         0\n" +
+			"	catalogname   null\n" +
+			"	schemaname    sys\n" +
+			"	tablename     test_rmetadata\n" +
+			"	autoincrement false\n" +
+			"	casesensitive true\n" +
+			"	currency      false\n" +
+			"	defwritable   false\n" +
+			"	nullable      1\n" +
+			"	readonly      true\n" +
+			"	searchable    true\n" +
+			"	signed        false\n" +
+			"	writable      false\n" +
+			"Colnr 5.\n" +
+			"	classname     java.lang.String\n" +
+			"	displaysize   11\n" +
+			"	label         myclob\n" +
+			"	name          myclob\n" +
+			"	type          12\n" +
+			"	typename      clob\n" +
+			"	precision     11\n" +
+			"	scale         0\n" +
+			"	catalogname   null\n" +
+			"	schemaname    sys\n" +
+			"	tablename     test_rmetadata\n" +
+			"	autoincrement false\n" +
+			"	casesensitive true\n" +
+			"	currency      false\n" +
+			"	defwritable   false\n" +
+			"	nullable      1\n" +
+			"	readonly      true\n" +
+			"	searchable    true\n" +
+			"	signed        false\n" +
+			"	writable      false\n" +
+			"6.	(null)\n" +
+			"6.	(null)\n" +
+			"6.	(null)\n" +
+			"6.	(null)\n" +
+			"6.	(null)\n" +
+			"7.	java.lang.Integer is an instance of java.lang.Integer\n" +
+			"7.	java.lang.Double is an instance of java.lang.Double\n" +
+			"7.	java.lang.Boolean is an instance of java.lang.Boolean\n" +
+			"7.	java.lang.String is an instance of java.lang.String\n" +
+			"7.	java.lang.String is an instance of java.lang.String\n" +
+			"0. true	true\n");
+	}
+
+	private void Test_Rpositioning() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		ResultSet rs = null;
+		try {
+			stmt = con.createStatement();
+			// get a one rowed resultset
+			rs = stmt.executeQuery("SELECT 1");
+
+			// >> true: we should be before the first result now
+			sb.append("1. true\t").append(rs.isBeforeFirst()).append("\n");
+			// >> false: we're not at the first result
+			sb.append("2. false\t").append(rs.isFirst()).append("\n");
+			// >> true: there is one result, so we can call next once
+			sb.append("3. true\t").append(rs.next()).append("\n");
+			// >> false: we're not before the first row anymore
+			sb.append("4. false\t").append(rs.isBeforeFirst()).append("\n");
+			// >> true: we're at the first result
+			sb.append("5. true\t").append(rs.isFirst()).append("\n");
+			// >> false: we're on the last row
+			sb.append("6. false\t").append(rs.isAfterLast()).append("\n");
+			// >> true: see above
+			sb.append("7. true\t").append(rs.isLast()).append("\n");
+			// >> false: there is one result, so this is it
+			sb.append("8. false\t").append(rs.next()).append("\n");
+			// >> true: yes, we're at the end
+			sb.append("9. true\t").append(rs.isAfterLast()).append("\n");
+			// >> false: no we're one over it
+			sb.append("10. false\t").append(rs.isLast()).append("\n");
+			// >> false: another try to move on should still fail
+			sb.append("11. false\t").append(rs.next()).append("\n");
+			// >> true: and we should stay positioned after the last
+			sb.append("12.true\t").append(rs.isAfterLast()).append("\n");
+
+			rs.close();
+
+			// try the same with a scrollable result set
+			DatabaseMetaData dbmd = con.getMetaData();
+			rs = dbmd.getTableTypes();
+
+			// >> true: we should be before the first result now
+			sb.append("1. true\t").append(rs.isBeforeFirst()).append("\n");
+			// >> false: we're not at the first result
+			sb.append("2. false\t").append(rs.isFirst()).append("\n");
+			// >> true: there is one result, so we can call next once
+			sb.append("3. true\t").append(rs.next()).append("\n");
+			// >> false: we're not before the first row anymore
+			sb.append("4. false\t").append(rs.isBeforeFirst()).append("\n");
+			// >> true: we're at the first result
+			sb.append("5. true\t").append(rs.isFirst()).append("\n");
+			// move to last row
+			rs.last();
+			// >> false: we're on the last row
+			sb.append("6. false\t").append(rs.isAfterLast()).append("\n");
+			// >> true: see above
+			sb.append("7. true\t").append(rs.isLast()).append("\n");
+			// >> false: there is one result, so this is it
+			sb.append("8. false\t").append(rs.next()).append("\n");
+			// >> true: yes, we're at the end
+			sb.append("9. true\t").append(rs.isAfterLast()).append("\n");
+			// >> false: no we're one over it
+			sb.append("10. false\t").append(rs.isLast()).append("\n");
+			// >> false: another try to move on should still fail
+			sb.append("11. false\t").append(rs.next()).append("\n");
+			// >> true: and we should stay positioned after the last
+			sb.append("12. true\t").append(rs.isAfterLast()).append("\n");
+
+			rs.close();
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, rs);
+
+		compareExpectedOutput("Test_Rpositioning",
+			"1. true	true\n" +
+			"2. false	false\n" +
+			"3. true	true\n" +
+			"4. false	false\n" +
+			"5. true	true\n" +
+			"6. false	false\n" +
+			"7. true	true\n" +
+			"8. false	false\n" +
+			"9. true	true\n" +
+			"10. false	false\n" +
+			"11. false	false\n" +
+			"12.true	true\n" +
+			"1. true	true\n" +
+			"2. false	false\n" +
+			"3. true	true\n" +
+			"4. false	false\n" +
+			"5. true	true\n" +
+			"6. false	false\n" +
+			"7. true	true\n" +
+			"8. false	false\n" +
+			"9. true	true\n" +
+			"10. false	false\n" +
+			"11. false	false\n" +
+			"12. true	true\n");
+	}
+
+	private void Test_Rsqldata() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		ResultSet rs = null;
+		ResultSetMetaData rsmd = null;
+
+		try {
+			con.setAutoCommit(false);
+			// >> false: auto commit should be off now
+			sb.append("0. false\t").append(con.getAutoCommit()).append("\n");
+
+			stmt = con.createStatement();
+			stmt.executeUpdate("CREATE TABLE Test_Rsqldata ( myinet inet, myurl url )");
+
+			String InsertInto = "INSERT INTO Test_Rsqldata VALUES ";
+			// all NULLs
+			stmt.executeUpdate(InsertInto + "(NULL, NULL)");
+			// all filled in
+			stmt.executeUpdate(InsertInto + "('172.5.5.5' , 'http://www.monetdb.org/')");
+			stmt.executeUpdate(InsertInto + "('172.5.5.5/32' , 'http://www.monetdb.org/Home')");
+			stmt.executeUpdate(InsertInto + "('172.5.5.5/16' , 'http://www.monetdb.org/Home#someanchor')");
+			stmt.executeUpdate(InsertInto + "('172.5.5.5/26' , 'http://www.monetdb.org/?query=bla')");
+
+			rs = stmt.executeQuery("SELECT * FROM Test_Rsqldata");
+			rsmd = rs.getMetaData();
+
+			sb.append("0. ").append(rsmd.getColumnCount()).append(" columns:\n");
+			for (int col = 1; col <= rsmd.getColumnCount(); col++) {
+				sb.append(col).append("\n");
+				sb.append("\tclassname   ").append(rsmd.getColumnClassName(col)).append("\n");
+				sb.append("\tcatalogname ").append(rsmd.getCatalogName(col)).append("\n");
+				sb.append("\tschemaname  ").append(rsmd.getSchemaName(col)).append("\n");
+				sb.append("\ttablename   ").append(rsmd.getTableName(col)).append("\n");
+				sb.append("\tcolumnname  ").append(rsmd.getColumnName(col)).append("\n");
+			}
+
+			for (int i = 1; rs.next(); i++) {
+				for (int col = 1; col <= rsmd.getColumnCount(); col++) {
+					sb.append(i).append(".\t");
+					Object x = rs.getObject(col);
+					if (x == null) {
+						sb.append("<null>").append("\n");
+					} else {
+						sb.append(x.toString()).append("\n");
+						if (x instanceof INET) {
+							INET inet = (INET)x;
+							sb.append("\t").append(inet.getAddress()).append("/").append(inet.getNetmaskBits()).append("\n");
+							sb.append("\t").append(inet.getInetAddress().toString()).append("\n");
+						} else if (x instanceof URL) {
+							URL url = (URL)x;
+							sb.append("\t").append(url.getURL().toString()).append("\n");
+						}
+					}
+				}
+			}
+
+			rs.close();
+
+			con.rollback();
+			con.setAutoCommit(true);
+			// >> true: auto commit was just switched on
+			sb.append("0. true\t").append(con.getAutoCommit()).append("\n");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, rs);
+
+		compareExpectedOutput("Test_Rsqldata",
+			"0. false	false\n" +
+			"0. 2 columns:\n" +
+			"1\n" +
+			"	classname   org.monetdb.jdbc.types.INET\n" +
+			"	catalogname null\n" +
+			"	schemaname  sys\n" +
+			"	tablename   test_rsqldata\n" +
+			"	columnname  myinet\n" +
+			"2\n" +
+			"	classname   org.monetdb.jdbc.types.URL\n" +
+			"	catalogname null\n" +
+			"	schemaname  sys\n" +
+			"	tablename   test_rsqldata\n" +
+			"	columnname  myurl\n" +
+			"1.	<null>\n" +
+			"1.	<null>\n" +
+			"2.	172.5.5.5\n" +
+			"	172.5.5.5/32\n" +
+			"	/172.5.5.5\n" +
+			"2.	http://www.monetdb.org/\n" +
+			"	http://www.monetdb.org/\n" +
+			"3.	172.5.5.5\n" +
+			"	172.5.5.5/32\n" +
+			"	/172.5.5.5\n" +
+			"3.	http://www.monetdb.org/Home\n" +
+			"	http://www.monetdb.org/Home\n" +
+			"4.	172.5.5.5/16\n" +
+			"	172.5.5.5/16\n" +
+			"	/172.5.5.5\n" +
+			"4.	http://www.monetdb.org/Home#someanchor\n" +
+			"	http://www.monetdb.org/Home#someanchor\n" +
+			"5.	172.5.5.5/26\n" +
+			"	172.5.5.5/26\n" +
+			"	/172.5.5.5\n" +
+			"5.	http://www.monetdb.org/?query=bla\n" +
+			"	http://www.monetdb.org/?query=bla\n" +
+			"0. true	true\n");
+	}
+
+	private void Test_Rtimedate() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		ResultSet rs = null;
+		try {
+			con.setAutoCommit(false);
+			// >> false: auto commit should be off now
+			sb.append("0. false\t").append(con.getAutoCommit()).append("\n");
+
+			stmt = con.createStatement();
+			stmt.executeUpdate("CREATE TABLE table_Test_Rtimedate ( id int PRIMARY KEY, ts timestamp, t time, d date, vc varchar(30) )");
+
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, ts) VALUES (1, timestamp '2004-04-24 11:43:53.123')");
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, t) VALUES (2, time '11:43:53.123')");
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, d) VALUES (3, date '2004-04-24')");
+			// same values but now as strings to test string to timestamp / time / date object conversions
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, vc) VALUES (4, '2004-04-24 11:43:53.654321')");
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, vc) VALUES (5, '11:43:53')");
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, vc) VALUES (6, '2004-04-24')");
+
+			// test also with small years (< 1000) (see bug 6468)
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, ts) VALUES (11, timestamp '904-04-24 11:43:53.567')");
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, ts) VALUES (12, timestamp '74-04-24 11:43:53.567')");
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, ts) VALUES (13, timestamp '4-04-24 11:43:53.567')");
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, d) VALUES (14, date '904-04-24')");
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, d) VALUES (15, date '74-04-24')");
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, d) VALUES (16, date '4-04-24')");
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, vc) VALUES (17, '904-04-24 11:43:53.567')");
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, vc) VALUES (18, '74-04-24 11:43:53.567')");
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, vc) VALUES (19, '4-04-24 11:43:53.567')");
+
+			// test also with negative years (see bug 6468)
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, ts) VALUES (21, timestamp '-4-04-24 11:43:53.567')");	// negative year
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, ts) VALUES (22, timestamp '-2004-04-24 11:43:53.567')"); // negative year
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, d) VALUES (23, date '-4-04-24')");	// negative year
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, d) VALUES (24, date '-3004-04-24')");	// negative year
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, vc) VALUES (25, '-2004-04-24 11:43:53.654321')");	// negative year
+			stmt.executeUpdate("INSERT INTO table_Test_Rtimedate(id, vc) VALUES (26, '-3004-04-24')");	// negative year
+
+			rs = stmt.executeQuery("SELECT * FROM table_Test_Rtimedate");
+
+			readNextRow(rs, 1, "ts");
+			readNextRow(rs, 2, "t");
+			readNextRow(rs, 3, "d");
+
+			readNextRow(rs, 4, "vc");
+			readNextRow(rs, 5, "vc");
+			readNextRow(rs, 6, "vc");
+
+			readNextRow(rs, 11, "ts");
+			readNextRow(rs, 12, "ts");
+			readNextRow(rs, 13, "ts");
+			readNextRow(rs, 14, "d");
+			readNextRow(rs, 15, "d");
+			readNextRow(rs, 16, "d");
+			readNextRow(rs, 17, "vc");
+			readNextRow(rs, 18, "vc");
+			readNextRow(rs, 19, "vc");
+
+			readNextRow(rs, 21, "ts");
+			readNextRow(rs, 22, "ts");
+			readNextRow(rs, 23, "d");
+			readNextRow(rs, 24, "d");
+			readNextRow(rs, 25, "vc");
+			readNextRow(rs, 26, "vc");
+
+			readWarnings(stmt.getWarnings());
+			readWarnings(con.getWarnings());
+
+			con.rollback();
+			con.setAutoCommit(true);
+			// >> true: auto commit should be on by default
+			sb.append("0. true\t").append(con.getAutoCommit()).append("\n");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, rs);
+
+		compareExpectedOutput("Test_Rtimedate",
+			"0. false	false\n" +
+			"1. ts 2004-04-24 11:43:53.123000 to ts: 2004-04-24 11:43:53.123\n" +
+			"1. ts 2004-04-24 11:43:53.123000 to tm: 11:43:53\n" +
+			"1. ts 2004-04-24 11:43:53.123000 to dt: 2004-04-24\n" +
+			"2. t 11:43:53 to ts: 1970-01-01 11:43:53.0\n" +
+			"2. t 11:43:53 to tm: 11:43:53\n" +
+			"2. t 11:43:53 to dt: 1970-01-01\n" +
+			"3. d 2004-04-24 to ts: 2004-04-24 00:00:00.0\n" +
+			"3. d 2004-04-24 to tm: 00:00:00\n" +
+			"3. d 2004-04-24 to dt: 2004-04-24\n" +
+			"4. vc 2004-04-24 11:43:53.654321 to ts: 2004-04-24 11:43:53.654321\n" +
+			"rs.getTime(colnm) failed with error: parsing failed, found: '-' in: \"2004-04-24 11:43:53.654321\" at pos: 5\n" +
+			"4. vc 2004-04-24 11:43:53.654321 to dt: 2004-04-24\n" +
+			"rs.getTimestamp(colnm) failed with error: parsing failed, found: ':' in: \"11:43:53\" at pos: 3\n" +
+			"5. vc 11:43:53 to tm: 11:43:53\n" +
+			"rs.getDate(colnm) failed with error: parsing failed, found: ':' in: \"11:43:53\" at pos: 3\n" +
+			"6. vc 2004-04-24 to ts: 2004-04-24 00:00:00.0\n" +
+			"rs.getTime(colnm) failed with error: parsing failed, found: '-' in: \"2004-04-24\" at pos: 5\n" +
+			"6. vc 2004-04-24 to dt: 2004-04-24\n" +
+			"11. ts 904-04-24 11:43:53.567000 to ts: 0904-04-24 11:43:53.567\n" +
+			"11. ts 904-04-24 11:43:53.567000 to tm: 11:43:53\n" +
+			"11. ts 904-04-24 11:43:53.567000 to dt: 0904-04-24\n" +
+			"12. ts 74-04-24 11:43:53.567000 to ts: 0074-04-24 11:43:53.567\n" +
+			"12. ts 74-04-24 11:43:53.567000 to tm: 11:43:53\n" +
+			"12. ts 74-04-24 11:43:53.567000 to dt: 0074-04-24\n" +
+			"13. ts 4-04-24 11:43:53.567000 to ts: 0004-04-24 11:43:53.567\n" +
+			"13. ts 4-04-24 11:43:53.567000 to tm: 11:43:53\n" +
+			"13. ts 4-04-24 11:43:53.567000 to dt: 0004-04-24\n" +
+			"14. d 904-04-24 to ts: 0904-04-24 00:00:00.0\n" +
+			"14. d 904-04-24 to tm: 00:00:00\n" +
+			"14. d 904-04-24 to dt: 0904-04-24\n" +
+			"15. d 74-04-24 to ts: 0074-04-24 00:00:00.0\n" +
+			"15. d 74-04-24 to tm: 00:00:00\n" +
+			"15. d 74-04-24 to dt: 0074-04-24\n" +
+			"16. d 4-04-24 to ts: 0004-04-24 00:00:00.0\n" +
+			"16. d 4-04-24 to tm: 00:00:00\n" +
+			"16. d 4-04-24 to dt: 0004-04-24\n" +
+			"17. vc 904-04-24 11:43:53.567 to ts: 0904-04-24 11:43:53.567\n" +
+			"rs.getTime(colnm) failed with error: parsing failed, found: '-' in: \"904-04-24 11:43:53.567\" at pos: 4\n" +
+			"17. vc 904-04-24 11:43:53.567 to dt: 0904-04-24\n" +
+			"18. vc 74-04-24 11:43:53.567 to ts: 0074-04-24 11:43:53.567\n" +
+			"rs.getTime(colnm) failed with error: parsing failed, found: '-' in: \"74-04-24 11:43:53.567\" at pos: 3\n" +
+			"18. vc 74-04-24 11:43:53.567 to dt: 0074-04-24\n" +
+			"19. vc 4-04-24 11:43:53.567 to ts: 0004-04-24 11:43:53.567\n" +
+			"rs.getTime(colnm) failed with error: parsing failed, found: '-' in: \"4-04-24 11:43:53.567\" at pos: 2\n" +
+			"19. vc 4-04-24 11:43:53.567 to dt: 0004-04-24\n" +
+			"21. ts -4-04-24 11:43:53.567000 to ts: 0004-04-24 11:43:53.567\n" +
+			"21. ts -4-04-24 11:43:53.567000 to tm: 11:43:53\n" +
+			"21. ts -4-04-24 11:43:53.567000 to dt: 0004-04-24\n" +
+			"22. ts -2004-04-24 11:43:53.567000 to ts: 2004-04-24 11:43:53.567\n" +
+			"22. ts -2004-04-24 11:43:53.567000 to tm: 11:43:53\n" +
+			"22. ts -2004-04-24 11:43:53.567000 to dt: 2004-04-24\n" +
+			"23. d -4-04-24 to ts: 0004-04-24 00:00:00.0\n" +
+			"23. d -4-04-24 to tm: 00:00:00\n" +
+			"23. d -4-04-24 to dt: 0004-04-24\n" +
+			"24. d -3004-04-24 to ts: 3004-04-24 00:00:00.0\n" +
+			"24. d -3004-04-24 to tm: 00:00:00\n" +
+			"24. d -3004-04-24 to dt: 3004-04-24\n" +
+			"25. vc -2004-04-24 11:43:53.654321 to ts: 2004-04-24 11:43:53.654321\n" +
+			"rs.getTime(colnm) failed with error: parsing failed, found: '-' in: \"-2004-04-24 11:43:53.654321\" at pos: 6\n" +
+			"25. vc -2004-04-24 11:43:53.654321 to dt: 2004-04-24\n" +
+			"26. vc -3004-04-24 to ts: 3004-04-24 00:00:00.0\n" +
+			"rs.getTime(colnm) failed with error: parsing failed, found: '-' in: \"-3004-04-24\" at pos: 6\n" +
+			"26. vc -3004-04-24 to dt: 3004-04-24\n" +
+			"0. true	true\n");
+	}
+
+	private void readNextRow(ResultSet rs, int rowseq, String colnm) throws SQLException {
+		rs.next();
+		readWarnings(rs.getWarnings());
+		rs.clearWarnings();
+
+		// fetch the column value using multiple methods: getString(), getTimestamp(), getTime() and getDate()
+		// to test proper conversion and error reporting
+		String data = rs.getString("id") + ". " + colnm + " " + rs.getString(colnm) + " to ";
+
+		// getTimestamp() may raise a conversion warning when the value is of type Time or a String which doesn't match format yyyy-mm-dd hh:mm:ss
+		try {
+			sb.append(data + "ts: " + rs.getTimestamp(colnm)).append("\n");
+		} catch (SQLException e) {
+			sb.append("rs.getTimestamp(colnm) failed with error: ").append(e.getMessage()).append("\n");
+		}
+		readWarnings(rs.getWarnings());
+		rs.clearWarnings();
+
+		// getTime() may raise a conversion warning when the value is of type Date or a String which doesn't match format hh:mm:ss
+		try {
+			sb.append(data + "tm: " + rs.getTime(colnm)).append("\n");
+		} catch (SQLException e) {
+			sb.append("rs.getTime(colnm) failed with error: ").append(e.getMessage()).append("\n");
+		}
+		readWarnings(rs.getWarnings());
+		rs.clearWarnings();
+
+		// getDate() may raise a conversion warning when the value is of type Time or a String which doesn't match format yyyy-mm-dd
+		try {
+			sb.append(data + "dt: " + rs.getDate(colnm)).append("\n");
+		} catch (SQLException e) {
+			sb.append("rs.getDate(colnm) failed with error: ").append(e.getMessage()).append("\n");
+		}
+		readWarnings(rs.getWarnings());
+		rs.clearWarnings();
+	}
+
+	private void Test_Sbatching() {
+		sb.setLength(0);	// clear the output log buffer
+
+		Statement stmt = null;
+		PreparedStatement pstmt = null;
+		ResultSet rs = null;
+		try {
+			con.setAutoCommit(false);
+			// >> false: auto commit should be off now
+			sb.append("0. false\t").append(con.getAutoCommit()).append("\n");
+
+			stmt = con.createStatement();
+
+			sb.append("1. create...");
+			if (stmt.executeUpdate("CREATE TABLE Test_Sbatching ( id int )") != Statement.SUCCESS_NO_INFO)
+				sb.append("Wrong return status\n");
+			else
+				sb.append("passed\n");
+
+			// start batching a large amount of inserts
+			for (int i = 1; i <= 3432; i++) {
+				stmt.addBatch("INSERT INTO Test_Sbatching VALUES (" + i + ")");
+				if (i % 1500 == 0) {
+					sb.append("2. executing batch (1500 inserts)...");
+					int[] cnts = stmt.executeBatch();
+					sb.append("passed\n");
+					sb.append("3. checking number of update counts...");
+					if (cnts.length != 1500)
+						sb.append("Invalid size: ").append(cnts.length);
+					sb.append(cnts.length).append(" passed\n");
+					sb.append("4. checking update counts (should all be 1)...");
+					for (int j = 0; j < cnts.length; j++) {
+						if (cnts[j] != 1)
+							sb.append("Unexpected value: ").append(cnts[j]);
+					}
+					sb.append("passed\n");
+					con.commit();
+				}
+			}
+			sb.append("5. executing final batch...");
+			stmt.executeBatch();
+			con.commit();
+			sb.append("passed\n");
+
+			pstmt = con.prepareStatement("INSERT INTO Test_Sbatching VALUES (?)");
+			// start batching a large amount of prepared inserts using JDBC 4.2 executeLargeBatch()
+			for (int i = 1; i <= 3568; i++) {
+				pstmt.setInt(1, i);
+				pstmt.addBatch();
+				if (i % 3000 == 0) {
+					sb.append("2. executing batch (3000 inserts)...");
+					long[] cnts = pstmt.executeLargeBatch();
+					sb.append("passed\n");
+					sb.append("3. checking number of update counts...");
+					if (cnts.length != 3000)
+						sb.append("Invalid size: ").append(cnts.length);
+					sb.append(cnts.length).append(" passed\n");
+					sb.append("4. checking update counts (should all be 1)...");
+					for (int j = 0; j < cnts.length; j++) {
+						if (cnts[j] != 1)
+							sb.append("Unexpected value: ").append(cnts[j]);
+					}
+					sb.append("passed\n");
+					con.commit();
+				}
+			}
+			sb.append("5. executing final Largebatch...");
+			pstmt.executeLargeBatch();
+			con.commit();
+			sb.append("passed\n");
+
+			sb.append("6. clearing the batch...");
+			stmt.clearBatch();
+			pstmt.clearBatch();
+			sb.append("passed\n");
+
+			sb.append("7. checking table count...");
+			rs = stmt.executeQuery("SELECT COUNT(*) FROM Test_Sbatching");
+			rs.next();
+			sb.append(rs.getInt(1)).append(" passed\n");
+
+			sb.append("8. drop table...");
+			if (stmt.executeUpdate("DROP TABLE Test_Sbatching") != Statement.SUCCESS_NO_INFO)
+				sb.append("Wrong return status\n");
+			else
+				sb.append("passed\n");
+
+			// rs.close();
+			stmt.close();
+			pstmt.close();
+
+			con.commit();
+			con.setAutoCommit(true);
+			// >> true: auto commit should be on by default
+			sb.append("0. true\t").append(con.getAutoCommit()).append("\n");
+		} catch (SQLException e) {
+			sb.append("FAILED: ").append(e.getMessage()).append("\n");
+		}
+
+		closeStmtResSet(stmt, rs);
+		closeStmtResSet(pstmt, null);
+
+		compareExpectedOutput("Test_Sbatching",
+			"0. false	false\n" +
+			"1. create...passed\n" +
+			"2. executing batch (1500 inserts)...passed\n" +
+			"3. checking number of update counts...1500 passed\n" +
+			"4. checking update counts (should all be 1)...passed\n" +
+			"2. executing batch (1500 inserts)...passed\n" +
+			"3. checking number of update counts...1500 passed\n" +
+			"4. checking update counts (should all be 1)...passed\n" +
+			"5. executing final batch...passed\n" +
+			"2. executing batch (3000 inserts)...passed\n" +
+			"3. checking number of update counts...3000 passed\n" +
+			"4. checking update counts (should all be 1)...passed\n" +
+			"5. executing final Largebatch...passed\n" +
+			"6. clearing the batch...passed\n" +
+			"7. checking table count...7000 passed\n" +
+			"8. drop table...passed\n" +
+			"0. true	true\n");
+	}
+
+	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").append(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: ").append(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).append(". isWrapperFor(").append(classnm).append(") returns: ").append(isWrapper);
+			if (isWrapper) {
+				Object wobj = obj.unwrap(clazz);
+				sb.append("\tCalled unwrap(). Returned object is ").append((wobj != null ? "not null, so oke" : "null !!"));
+			}
+			sb.append("\n");
+		} catch (ClassNotFoundException cnfe) {
+			sb.append(cnfe.toString());
+		} catch (SQLException se) {
+			sb.append(se.getMessage());
+		}
+	}
+
+	// some private utility methods for showing table content and params meta data
+	private void showTblContents(String tblnm) {
+		Statement stmt = null;
+		ResultSet rs = null;
+		try {
+			stmt = con.createStatement();
+			rs = stmt.executeQuery("SELECT * FROM " + tblnm);
+			if (rs != null) {
+				ResultSetMetaData rsmd = rs.getMetaData();
+				sb.append("Table ").append(tblnm).append(" has ").append(rsmd.getColumnCount()).append(" columns:").append("\n");
+				for (int col = 1; col <= rsmd.getColumnCount(); col++) {
+					sb.append("\t").append(rsmd.getColumnLabel(col));
+				}
+				sb.append("\n");
+				while (rs.next()) {
+					for (int col = 1; col <= rsmd.getColumnCount(); col++) {
+						sb.append("\t").append(rs.getString(col));
+					}
+					sb.append("\n");
+				}
+			} else
+				sb.append("failed to execute query: SELECT * FROM ").append(tblnm).append("\n");
+		} catch (SQLException e) {
+			sb.append("showContents failed: ").append(e.getMessage()).append("\n");
+		}
+		closeStmtResSet(stmt, rs);
+	}
+
+	private void showParams(PreparedStatement pstmt) {
+		try {
+			// testing and showing parameter meta data
+			ParameterMetaData pmd = pstmt.getParameterMetaData();
+			sb.append("pmd. ").append(pmd.getParameterCount()).append(" parameters:\n");
+			for (int parm = 1; parm <= pmd.getParameterCount(); parm++) {
+				sb.append("Param ").append(parm).append("\n");
+				int nullable = pmd.isNullable(parm);
+				sb.append("  nullable  ").append(nullable).append(" (");
+				switch (nullable) {
+					case ParameterMetaData.parameterNoNulls:	sb.append("NO"); break;
+					case ParameterMetaData.parameterNullable:	sb.append("YA"); break;
+					case ParameterMetaData.parameterNullableUnknown:	sb.append("UNKNOWN"); break;
+					default: sb.append("INVALID ").append(nullable); break;
+				}
+				sb.append(")\n");
+				sb.append("  signed    ").append(pmd.isSigned(parm)).append("\n");
+				sb.append("  precision ").append(pmd.getPrecision(parm)).append("\n");
+				sb.append("  scale     ").append(pmd.getScale(parm)).append("\n");
+				sb.append("  type      ").append(pmd.getParameterType(parm)).append("\n");
+				sb.append("  typename  ").append(pmd.getParameterTypeName(parm)).append("\n");
+				sb.append("  classname ").append(pmd.getParameterClassName(parm)).append("\n");
+				int mode = pmd.getParameterMode(parm);
+				sb.append("  mode      ").append(mode).append(" (");
+				switch (mode) {
+					case ParameterMetaData.parameterModeIn:	sb.append("IN"); break;
+					case ParameterMetaData.parameterModeInOut:	sb.append("INOUT"); break;
+					case ParameterMetaData.parameterModeOut:	sb.append("OUT"); break;
+					case ParameterMetaData.parameterModeUnknown:	sb.append("UNKNOWN"); break;
+					default: sb.append("INVALID ").append(mode); break;
+				}
+				sb.append(")\n");
+			}
+		} catch (SQLException e) {
+			sb.append("showParams() FAILED: ").append(e.getMessage()).append("\n");
+		}
+	}
+
+	private void readExceptions(SQLException e) {
+		while (e != null) {
+			sb.append("Exception: ").append(e.toString()).append("\n");
+			e = e.getNextException();
+		}
+	}
+
+	private void readWarnings(SQLWarning w) {
+		while (w != null) {
+			sb.append("Warning: ").append(w.toString()).append("\n");
+			w = w.getNextWarning();
+		}
+	}
+
+	private void compareExpectedOutput(String testname, String expected) {
+		if (!expected.equals(sb.toString())) {
+			System.out.print("Test '");
+			System.out.print(testname);
+			if (!testname.endsWith(")"))
+				System.out.print("()");
+			System.out.println("' produced different output!");
+			System.out.println("Expected:");
+			System.out.println(expected);
+			System.out.println("Gotten:");
+			System.out.println(sb);
+			System.out.println();
+		}
+		if (sb.length() > sbInitLen) {
+			System.out.println("Test '" + testname
+				+ "' produced output > " + sbInitLen
+				+ " chars! Enlarge sbInitLen to: " + sb.length());
+		}
+	}
+
+	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/SQLcopyinto.java
+++ b/tests/SQLcopyinto.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_CallableStmt.java
+++ b/tests/Test_CallableStmt.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Cautocommit.java
+++ b/tests/Test_Cautocommit.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Cforkbomb.java
+++ b/tests/Test_Cforkbomb.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_CisValid.java
+++ b/tests/Test_CisValid.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Clargequery.java
+++ b/tests/Test_Clargequery.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Cmanycon.java
+++ b/tests/Test_Cmanycon.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Creplysize.java
+++ b/tests/Test_Creplysize.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Csavepoints.java
+++ b/tests/Test_Csavepoints.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Csendthread.java
+++ b/tests/Test_Csendthread.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Ctransaction.java
+++ b/tests/Test_Ctransaction.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Dobjects.java
+++ b/tests/Test_Dobjects.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_FetchSize.java
+++ b/tests/Test_FetchSize.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Int128.java
+++ b/tests/Test_Int128.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.math.BigDecimal;
--- a/tests/Test_PSgeneratedkeys.java
+++ b/tests/Test_PSgeneratedkeys.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_PSgetObject.java
+++ b/tests/Test_PSgetObject.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_PSlargeamount.java
+++ b/tests/Test_PSlargeamount.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_PSlargebatchval.java
+++ b/tests/Test_PSlargebatchval.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_PSlargeresponse.java
+++ b/tests/Test_PSlargeresponse.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_PSmanycon.java
+++ b/tests/Test_PSmanycon.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_PSmetadata.java
+++ b/tests/Test_PSmetadata.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_PSsomeamount.java
+++ b/tests/Test_PSsomeamount.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_PSsqldata.java
+++ b/tests/Test_PSsqldata.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_PStimedate.java
+++ b/tests/Test_PStimedate.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_PStimezone.java
+++ b/tests/Test_PStimezone.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_PStypes.java
+++ b/tests/Test_PStypes.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Rbooleans.java
+++ b/tests/Test_Rbooleans.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Rmetadata.java
+++ b/tests/Test_Rmetadata.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Rpositioning.java
+++ b/tests/Test_Rpositioning.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Rsqldata.java
+++ b/tests/Test_Rsqldata.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Rtimedate.java
+++ b/tests/Test_Rtimedate.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Sbatching.java
+++ b/tests/Test_Sbatching.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Smoreresults.java
+++ b/tests/Test_Smoreresults.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/Test_Wrapper.java
+++ b/tests/Test_Wrapper.java
@@ -3,7 +3,7 @@
  * 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.
+ * Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
  */
 
 import java.sql.*;
--- a/tests/build.xml
+++ b/tests/build.xml
@@ -5,7 +5,7 @@ This Source Code Form is subject to the 
 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.
+Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
 -->
 
 <!--
@@ -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" />
--- a/tests/drop.sql
+++ b/tests/drop.sql
@@ -2,7 +2,7 @@
 -- 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.
+-- Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
 
 drop table "foreign";
 drop table allnewtriples;
--- a/version.sh
+++ b/version.sh
@@ -4,7 +4,7 @@
 # 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.
+# Copyright 1997 - July 2008 CWI, August 2008 - 2021 MonetDB B.V.
 
 if [[ -z $1 ]] ; then
 	echo "Usage: $0 [-w] <(jdbc|mcl)> <(major|minor|suffix)=newversion> [...]"