changeset 487:35592dc59ecb

Merge remote-tracking branch 'github/master'
author GIT repo for MonetDB <monetgit@dev.monetdb.org>
date Wed, 16 Jun 2021 17:19:07 +0200 (2021-06-16)
parents 8e3fbb725faf (diff) 65d3f718fe25 (current diff)
children 59309e3e6daa
files
diffstat 17 files changed, 546 insertions(+), 465 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags
+++ b/.hgtags
@@ -5,3 +5,4 @@ fe8170e2b549c22ceb2d96301022b9304f62424d
 6423fb0bf9ebba64167ca1b40b79aeacbb8e7c47 v2.28
 70808ab71d2ad997d7b7fa38d675fc90f71a059c v2.29
 bc39810b3faa4c52ce63b29147075e91266a3e84 v3.0
+a80b5db244b8ceeea936770b9c5f314d46251fb3 v3.1
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,179 +1,37 @@
 # ChangeLog file for monetdb-java
 # This file is updated with Maddlog
 
+* Mon Jun 14 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
+- Compiled and released new jar files: monetdb-jdbc-3.1.jre8.jar,
+  monetdb-mcl-1.20.jre8.jar and jdbcclient.jre8.jar
+
+* Thu Apr 29 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
+- Improved performance of ResultSetMetaData methods getPrecision(),
+  getScale(), isNullable() and isAutoIncrement(). The data is fetched
+  from the server by sending a query. This used to be one query for
+  each column of the ResultSet. Now these metadata queries are combined
+  into one query fetching this meta data for up to 50 columns in one query.
+  This reduces the number of queries sent to the server significantly.
+  This is noticable for instance when using generic JDBC query tools
+  such as SQuirreL, DBeaver, which now respond much faster.
+
 * Wed Mar  3 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
 - Implemented PreparedStatement.toString() as requested by
   https://github.com/MonetDB/monetdb-java/issues/8
 
 * Wed Mar  3 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
 - Implemented fix for released monetdb-jdbc-3.0.jre8.jar and
-  monetdb-mcl-1.19.jre8.jar when it is was run using java build
-  1.8.0_###.  It would throw:  java.lang.NoSuchMethodError:
-  java.nio.CharBuffer.mark()Ljava/nio/CharBuffer; at
-  org.monetdb.mcl.parser.StartOfHeaderParser.getNextAsString(Unknown
-  Source) at
-  org.monetdb.jdbc.MonetConnection$ResponseList.executeQuery(Unknown
-  Source) at
-  org.monetdb.jdbc.MonetConnection$ResponseList.processQuery(Unknown
-  Source) at org.monetdb.jdbc.MonetStatement.internalExecute(Unknown
-  Source) at org.monetdb.jdbc.MonetStatement.execute(Unknown Source)
+  monetdb-mcl-1.19.jre8.jar when it is was run using java build 1.8.0_###.
+  It would throw:
+  java.lang.NoSuchMethodError: java.nio.CharBuffer.mark()Ljava/nio/CharBuffer;
+  at org.monetdb.mcl.parser.StartOfHeaderParser.getNextAsString(Unknown Source)
+  at org.monetdb.jdbc.MonetConnection$ResponseList.executeQuery(Unknown Source)
+  at org.monetdb.jdbc.MonetConnection$ResponseList.processQuery(Unknown Source)
+  at org.monetdb.jdbc.MonetStatement.internalExecute(Unknown Source)
+  at org.monetdb.jdbc.MonetStatement.execute(Unknown Source)
   The problem is caused by a change in java.nio.CharBuffer API (return
   types of methods mark() and reset() have changed from Buffer to
   CharBuffer) from Java 8 to Java 9+.
 
-* Wed Feb 17 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- Compiled and released new jar files: monetdb-jdbc-3.0.jre8.jar,
-  monetdb-mcl-1.19.jre8.jar and jdbcclient.jre8.jar
-
-  monetdb-jdbc-3.0.jre8.jar is a new major release of the MonetDB JDBC driver.
-  The MonetDB JDBC Driver is now compliant with the Javaâ„¢ Database
-  Connectivity (JDBC) 4.2 specification as defined in Java 8 and requires
-  Java 8 runtime (profile compact2) as minimum version.
-
-  Important: the MonetDB JDBC driver class name has also been changed in
-  this release to: org.monetdb.jdbc.MonetDriver.  The old driver class
-  (nl.cwi.monetdb.jdbc.MonetDriver) is also included in the jar file, but
-  only to ease the transition for existing deployments. It will be removed
-  in a future release of this JDBC driver. Please use the new driver
-  class name if this is used in your configuration files or Java code.
-
-  The JdbcClient program (jdbcclient.jre8.jar) has been extended with
-  functionality to validate the integrity of the system tables (\vsci) or
-  to validate the integrity of data in tables of a specific schema (\vsi xyz)
-  based on defined declarative constraints (pkey, fkey, not null, etc.).
-  This will be usefull to find and report inconsistencies in your database.
-  This functionality is a beta release. Please let us know if you
-  encounter any issues running it. See below for more information.
-
-  Besides a few bug fixes also performance has been improved in multiple areas.
-
-* Wed Feb  3 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- Added support for escaped wildcards (\% en \_) in String arguments of
-  DatabaseMetaData methods which return a ResultSet, such as getTables(),
-  getColumns(), etc.  When you do not want the characters % or _ to be
-  interpreted as wildcards but as normal characters you can prefix them
-  with a backslash (so \% and \_). Note: be sure all wildcards characters
-  in the String argument are escaped else the search must still use a
-  LIKE operator instead of an = comparison operator.
-  This fixes: https://github.com/MonetDB/monetdb-java/issues/3
-
-* Thu Jan 28 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- Corrected the ordering of the output of DatabaseMetaData methods
-  getImportedKeys(), getExportedKeys() and getCrossReference(). In cases
-  where a table would have multiple fks to the same external table,
-  the output was not as expected. This has been corrected, so the columns
-  now appear in the order as defined in the creation of the fks.
-
-* Thu Jan 28 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- The dumping of table definitions from JdbcClient program has been
-  improved. It now includes the ON UPDATE and ON DELETE rules for foreign
-  key constraints. Also it no longer generates CREATE INDEX statements
-  for foreign key constraints whose name is not system generated but
-  user specified.
-
-* Thu Jan 14 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- Improved DatabaseMetaData.getTypeInfo() output for temporal data
-  types: sec_interval, day_interval, month_interval, date, time, timetz,
-  timestamp and timestamptz.
-
-* 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
-  Naming convention as MonetDB's main website is www.monetdb.org.
-  To prevent problems with existing Java programs and JDBC driver
-  configurations we still support usage of the following classes:
-   nl.cwi.monetdb.jdbc.MonetDriver
-   nl.cwi.monetdb.jdbc.types.INET
-   nl.cwi.monetdb.jdbc.types.URL
-   nl.cwi.monetdb.mcl.net.MapiSocket
-   nl.cwi.monetdb.client.JdbcClient
-  They are implemented as simple wrappers of their org.monetdb.* equivalents.
-  Note: These nl.cwi.monetdb.* classes are now marked as deprecated and may
-  be removed in a future release. If you still use them in your Java code or
-  configuration files, update them to use the new package names.
-
-* Thu Oct 29 2020 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- Extended JdbcClient program with 3 new commands to quickly validate
-  data integrity:
-  \vsci          validate sql system catalog integrity
-  \vsi <schema>  validate integrity of data in the given schema
-  \vdbi          validate integrity of data in all user schemas in the database
-  The current validations include:
-  - Primary Key uniqueness
-  - Primary Key column(s) being NOT NULL (currently only for \vsci)
-  - Unique constraint uniqueness
-  - Foreign Key referential integrity
-  - Column NOT NULL constraint
-  - Varchar(n) max length constraint
-  - Idem for char(n), clob(n), blob(n), json(n) and url(n).
-  It can be usefull to run \vsci before and after an upgrade of MonetDB server.
-  Use \vsi my_schema  to validate data in all tables of a specific schema.
-  Use \vdbi  to validate integrity of data in all user schemas in
-  the database. Note: this can take a while, depending on your number
-  of user schemas, tables, columns and rows. Despite being tested on several
-  internal dbs the functionality is still beta, so you can get false
-  errors reported. If you encounter any let us know asap.
-
-* Thu Oct  8 2020 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- Improved performance of ResultSetMetaData methods isAutoIncrement(),
-  getPrecision() and getScale() significantly for columns of specific data
-  types as in some cases no costly meta data query is executed anymore.
-
-* Thu Oct  8 2020 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- 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
-  as it results by default in less memory usage, (much) faster response
-  and better user experience for many generic JDBC applications (like
-  SQuirreL SQL, DBeaver, etc) when fetching data from CLOB or BLOB result
-  columns.  See release.txt for more information and how you can turn
-  it off to get the old JDBC driver behavior if you require it.
-
-* Wed Oct  7 2020 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- Added support for new Java 8 java.sql.Types: Types.TIME_WITH_TIMEZONE and
-  Types.TIMESTAMP_WITH_TIMEZONE.
-
-* Wed Sep 23 2020 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- Updated JDBC driver to comply with JDBC 4.2 interface now we compile
-  for Java 8. This includes:
-  - adding 8 methods to MonetCallableStatement
-  - adding 2 methods to MonetDatabaseMetaData
-  - adding 3 methods to MonetPreparedStatement
-  - adding 4 methods to MonetResultSet
-  - adding 8 methods to MonetStatement
-
-* Wed Sep 23 2020 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- Corrected MonetDatabaseMetaData.getTypeInfo()
-  - The LITERAL_PREFIX column now includes the required casting name for
-    types: clob, inet, json, url, uuid and blob.
-  - The SEARCHABLE column now returns typePredBasic instead of typeSearchable
-    for type: blob.
-  - The AUTO_INCREMENT column now returns false for types: hugeint, decimal,
-    oid and wrd.
-
-* Thu Sep 10 2020 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- Removed support for deprecated MD5 encryption algorithm in MapiSocket.
-
-* Wed Sep 9 2020 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- Corrected Statement.executeBatch() method. It now implicitly clears the
-  batch buffer, ready to accept new addBatch() calls without the need for
-  an explicit clearBatch() call.
-  See also https://www.monetdb.org/bugzilla/show_bug.cgi?id=6953
-
-* Wed Feb 19 2020 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- Corrected the return value of getCatalogTerm() to "cat".
-
-* Wed Feb 12 2020 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- As Java 7 is no longer supported we now compile for Java 8 as the
-  minimum required JVM version (profile compact2).
-
+For a complete list of changes in previous monetdb-java releases see:
+  https://www.monetdb.org/downloads/Java/archive/ChangeLog-Archive
--- a/ChangeLog-Archive
+++ b/ChangeLog-Archive
@@ -2,6 +2,38 @@
 # This file contains all past monetdb-java ChangeLog entries
 # For every new release the ChangeLog is prepended to this file.
 
+* Mon Jun 14 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
+- Compiled and released new jar files: monetdb-jdbc-3.1.jre8.jar,
+  monetdb-mcl-1.20.jre8.jar and jdbcclient.jre8.jar
+
+* Thu Apr 29 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
+- Improved performance of ResultSetMetaData methods getPrecision(),
+  getScale(), isNullable() and isAutoIncrement(). The data is fetched
+  from the server by sending a query. This used to be one query for
+  each column of the ResultSet. Now these metadata queries are combined
+  into one query fetching this meta data for up to 50 columns in one query.
+  This reduces the number of queries sent to the server significantly.
+  This is noticable for instance when using generic JDBC query tools
+  such as SQuirreL, DBeaver, which now respond much faster.
+
+* Wed Mar  3 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
+- Implemented PreparedStatement.toString() as requested by
+  https://github.com/MonetDB/monetdb-java/issues/8
+
+* Wed Mar  3 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
+- Implemented fix for released monetdb-jdbc-3.0.jre8.jar and
+  monetdb-mcl-1.19.jre8.jar when it is was run using java build 1.8.0_###.
+  It would throw:
+  java.lang.NoSuchMethodError: java.nio.CharBuffer.mark()Ljava/nio/CharBuffer;
+  at org.monetdb.mcl.parser.StartOfHeaderParser.getNextAsString(Unknown Source)
+  at org.monetdb.jdbc.MonetConnection$ResponseList.executeQuery(Unknown Source)
+  at org.monetdb.jdbc.MonetConnection$ResponseList.processQuery(Unknown Source)
+  at org.monetdb.jdbc.MonetStatement.internalExecute(Unknown Source)
+  at org.monetdb.jdbc.MonetStatement.execute(Unknown Source)
+  The problem is caused by a change in java.nio.CharBuffer API (return
+  types of methods mark() and reset() have changed from Buffer to
+  CharBuffer) from Java 8 to Java 9+.
+
 * Wed Feb 17 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
 - Compiled and released new jar files: monetdb-jdbc-3.0.jre8.jar,
   monetdb-mcl-1.19.jre8.jar and jdbcclient.jre8.jar
--- a/build.properties
+++ b/build.properties
@@ -9,7 +9,7 @@
 # major release number
 MCL_MAJOR=1
 # minor release number
-MCL_MINOR=19
+MCL_MINOR=20
 
 
 ##
@@ -19,7 +19,7 @@ MCL_MINOR=19
 # major release number
 JDBC_MAJOR=3
 # minor release number
-JDBC_MINOR=0
+JDBC_MINOR=1
 # an additional identifying string
 JDBC_VER_SUFFIX=Liberica
 # the default port to connect on, if no port given when using SQL
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
 
   <groupId>monetdb</groupId>
   <artifactId>monetdb-jdbc</artifactId>
-  <version>3.0</version>
+  <version>3.1</version>
   <name>${project.groupId}:${project.artifactId}</name>
   <description>MonetDB JDBC driver</description>
   <url>https://www.monetdb.org</url>
--- a/release.txt
+++ b/release.txt
@@ -1,6 +1,6 @@
 RELEASE NOTES
-MonetDB JDBC driver version 3.0 (Liberica/MCL-1.19)
-Release date: 2021-02-17
+MonetDB JDBC driver version 3.1 (Liberica/MCL-1.20)
+Release date: 2021-06-14
 
 The Java Database Connectivity (JDBC) API provides universal data access from
 the Java programming language.
@@ -11,6 +11,10 @@ For more information see https://www.mon
 The latest MonetDB JDBC driver can be downloaded from
 https://www.monetdb.org/downloads/Java/
 
+The sources for this JDBC driver and related Java programs can be found at:
+https://dev.monetdb.org/hg/monetdb-java/file/tip
+
+
 The MonetDB JDBC connection URL format to use is:
   jdbc:monetdb://<hostname>[:<portnr>]/<databasename>[?<property>=<value>[&<property>=<value>]]
 
@@ -58,6 +62,14 @@ For example:
 See also: https://www.monetdb.org/Documentation/SQLreference/Programming/JDBC
 
 
+The MonetDB JDBC driver class name is: org.monetdb.jdbc.MonetDriver
+This has been changed as of release 3.0 (monetdb-jdbc-3.0.jre8.jar).
+The old driver class (nl.cwi.monetdb.jdbc.MonetDriver) has been deprecated.
+It is still included in the jar file to ease the transition for existing deployments.
+However it will be removed in a future release of the MonetDB JDBC driver.
+Please use the new driver class name asap in your configuration files or Java code.
+
+
 JDBC COMPLIANCE
 The MonetDB JDBC driver is a type 4 driver (100% pure Java) and
 complies to JDBC 4.2 definition, see
@@ -79,6 +91,8 @@ please let us know at our bugtracker:
 
 Currently implemented JDBC 4.2 interfaces include:
   * java.sql.Driver
+    The next method is NOT useable/supported:
+    - getParentLogger
 
   * java.sql.Connection
     The next features/methods are NOT useable/supported:
@@ -127,7 +141,11 @@ Currently implemented JDBC 4.2 interface
     - getAsciiStream, getUnicodeStream
     - getNClob
     - getRef, getRowId, getSQLXML
-    - All methods related to updateable result sets
+    - getObject(column, Class<T> type)
+    - moveToCurrentRow, moveToInsertRow,
+    - All methods related to updateable result sets such as:
+      updateArray ... updateTimestamp, cancelRowUpdates,
+      deleteRow, insertRow, refreshRow
 
   * java.sql.ResultSetMetaData
 
@@ -151,6 +169,8 @@ Currently implemented JDBC 4.2 interface
             and by class: org.monetdb.jdbc.types.URL
 
   * javax.sql.DataSource (not tested)
+    The next method is NOT useable/supported:
+    - getParentLogger
 
 
 The following java.sql.* interfaces are NOT implemented:
--- a/src/main/java/org/monetdb/client/JdbcClient.java
+++ b/src/main/java/org/monetdb/client/JdbcClient.java
@@ -18,6 +18,7 @@ import org.monetdb.util.XMLExporter;
 
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
+import java.io.Console;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.File;
@@ -257,7 +258,11 @@ public class JdbcClient {	/* cannot (yet
 		// we need the password from the user, fetch it with a pseudo
 		// password protector
 		if (pass == null) {
-			final char[] tmp = System.console().readPassword("password: ");
+			final Console syscon = System.console();
+			char[] tmp = null;
+			if (syscon != null) {
+				tmp = syscon.readPassword("password: ");
+			}
 			if (tmp == null) {
 				System.err.println("Invalid password!");
 				System.exit(1);
@@ -332,26 +337,27 @@ public class JdbcClient {	/* cannot (yet
 			dbmd = null;
 		}
 
+		stmt = con.createStatement();	// is used by processInteractive(), processBatch(), doDump()
+
 		in = new BufferedReader(new InputStreamReader(System.in));
 		out = new PrintWriter(new BufferedWriter(new java.io.OutputStreamWriter(System.out)));
 
-		stmt = con.createStatement();	// is used by doDump
-
 		// see if we will have to perform a database dump (only in SQL mode)
 		if ("sql".equals(lang) && copts.getOption("dump").isPresent() && dbmd != null) {
+			final int argcount = copts.getOption("dump").getArgumentCount();
+
 			// use the given file for writing
 			oc = copts.getOption("file");
 			if (oc.isPresent())
 				out = new PrintWriter(new BufferedWriter(new java.io.FileWriter(oc.getArgument())));
 
-			// we only want user tables and views to be dumped, unless a specific table is requested
+			// we only want user tables and views to be dumped (DDL and optional data), unless a specific table is requested
 			final String[] types = {"TABLE","VIEW","MERGE TABLE","REMOTE TABLE","REPLICA TABLE","STREAM TABLE"};
 			// Future: fetch all type names using dbmd.getTableTypes() and construct String[] with all
 			// table type names excluding the SYSTEM ... ones and LOCAL TEMPORARY TABLE ones.
 
-			// request the list of tables available in the current schema in the database
-			ResultSet tbl = dbmd.getTables(null, con.getSchema(), null,
-							(copts.getOption("dump").getArgumentCount() == 0) ? types : null);
+			// request the list of tables/views available in the current schema in the database
+			ResultSet tbl = dbmd.getTables(null, con.getSchema(), null, (argcount == 0) ? types : null);
 			// fetch all tables and store them in a LinkedList of Table objects
 			final LinkedList<Table> tables = new LinkedList<Table>();
 			while (tbl.next()) {
@@ -379,7 +385,7 @@ public class JdbcClient {	/* cannot (yet
 				out.println("START TRANSACTION;\n");
 
 			// dump specific table(s) or not?
-			if (copts.getOption("dump").getArgumentCount() > 0) { // yes we do
+			if (argcount > 0) { // yes we do
 				final String[] dumpers = copts.getOption("dump").getArguments();
 				for (int i = 0; i < tables.size(); i++) {
 					Table ttmp = tables.get(i);
@@ -813,6 +819,8 @@ public class JdbcClient {	/* cannot (yet
 							MDBvalidator.validateDBIntegrity(con, true);
 						} else if (command.equals("\\vdbi_noheader")) {	// used only for internal automated testing
 							MDBvalidator.validateDBIntegrity(con, false);
+						} else {
+							doProcess = true;
 						}
 					} else if (command.startsWith("\\l") || command.startsWith("\\i")) {
 						String object = command.substring(2).trim();
@@ -903,7 +911,7 @@ public class JdbcClient {	/* cannot (yet
 
 		// execute the query, let the driver decide what type it is
 		int aff = -1;
-		boolean	nextRslt = stmt.execute(query, Statement.RETURN_GENERATED_KEYS);
+		boolean nextRslt = stmt.execute(query, Statement.RETURN_GENERATED_KEYS);
 		if (!nextRslt)
 			aff = stmt.getUpdateCount();
 		do {
@@ -959,8 +967,7 @@ public class JdbcClient {	/* cannot (yet
 		} while ((nextRslt = stmt.getMoreResults()) ||
 			 (aff = stmt.getUpdateCount()) != -1);
 
-		// if there were warnings for this statement,
-		// and/or connection show them!
+		// if there were warnings for this statement show them!
 		warn = stmt.getWarnings();
 		while (warn != null) {
 			System.err.println("Statement warning: " + warn.getMessage());
@@ -968,6 +975,7 @@ public class JdbcClient {	/* cannot (yet
 		}
 		stmt.clearWarnings();
 
+		// if there were warnings for this connection show them!
 		warn = con.getWarnings();
 		while (warn != null) {
 			// suppress warning when issueing a "set schema xyz;" command
@@ -1036,11 +1044,12 @@ public class JdbcClient {	/* cannot (yet
 		exporter.dumpSchema(dbmd, tableType, table.getSchem(), table.getName());
 		out.println();
 
-		// only dump data from real tables, not from views / MERGE / REMOTE / REPLICA tables
+		// only dump data from real tables, not from VIEWs / MERGE / REMOTE / REPLICA / STREAM tables
 		if (tableType.contains("TABLE")
 		&& !tableType.equals("MERGE TABLE")
 		&& !tableType.equals("REMOTE TABLE")
-		&& !tableType.equals("REPLICA TABLE")) {
+		&& !tableType.equals("REPLICA TABLE")
+		&& !tableType.equals("STREAM TABLE")) {
 			final ResultSet rs = stmt.executeQuery("SELECT * FROM " + table.getFqnameQ());
 			if (rs != null) {
 				exporter.dumpResultSet(rs);
--- a/src/main/java/org/monetdb/jdbc/MonetConnection.java
+++ b/src/main/java/org/monetdb/jdbc/MonetConnection.java
@@ -140,13 +140,16 @@ public class MonetConnection
 	private final int lang;
 
 	/** Whether or not BLOB is mapped to Types.VARBINARY instead of Types.BLOB within this connection */
-	private boolean treatBlobAsVarBinary = true;	// turned on by default for optimal performance (from JDBC Driver release 2.30 onwards)
+	private boolean treatBlobAsVarBinary = true;	// turned on by default for optimal performance (from JDBC Driver release 3.0 onwards)
 	/** Whether or not CLOB is mapped to Types.VARCHAR instead of Types.CLOB within this connection */
-	private boolean treatClobAsVarChar = true;	// turned on by default for optimal performance (from JDBC Driver release 2.30 onwards)
+	private boolean treatClobAsVarChar = true;	// turned on by default for optimal performance (from JDBC Driver release 3.0 onwards)
 
 	/** The last set query timeout on the server as used by Statement, PreparedStatement and CallableStatement */
 	protected int lastSetQueryTimeout;	// 0 means no timeout, which is the default on the server
 
+	/** A cache to reduce the number of DatabaseMetaData objects created by getMetaData() to maximum 1 per connection */
+	private DatabaseMetaData dbmd;
+
 
 	/**
 	 * Constructor of a Connection for MonetDB. At this moment the
@@ -586,10 +589,14 @@ public class MonetConnection
 	 */
 	@Override
 	public DatabaseMetaData getMetaData() throws SQLException {
+		// for debug: System.out.println("calling getMetaData()");
 		if (lang != LANG_SQL)
 			throw new SQLException("This method is only supported in SQL mode", "M0M04");
-
-		return new MonetDatabaseMetaData(this);
+		if (dbmd == null) {
+			// first use, construct it once and reuse it for all next calls
+			dbmd = new MonetDatabaseMetaData(this);
+		}
+		return dbmd;
 	}
 
 	/**
@@ -1193,8 +1200,8 @@ public class MonetConnection
 	 */
 	@Override
 	public String toString() {
-		return "MonetDB Connection (" + getJDBCURL() + ") " +
-				(closed ? "disconnected" : "connected");
+		return "MonetDB Connection (" + getJDBCURL()
+			+ (closed ? ") disconnected" : ") connected");
 	}
 
 	//== Java 1.6 methods (JDBC 4.0)
@@ -1687,10 +1694,12 @@ public class MonetConnection
 		checkNotClosed();
 		Statement st = null;
 		try {
+			final String callstmt;
 			// 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 + ")";
+			if ((getDatabaseMajorVersion() == 11) && (getDatabaseMinorVersion() < 37))
+				callstmt = "CALL sys.\"settimeout\"(" + millis + ")";
+			else
+				callstmt = "CALL sys.\"setquerytimeout\"(" + millis + ")";
 			// for debug: System.out.println("Before: " + callstmt);
 			st = createStatement();
 			st.execute(callstmt);
@@ -1860,8 +1869,8 @@ public class MonetConnection
 			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);
+					final int end = env_monet_version.indexOf('.');
+					databaseMajorVersion = Integer.parseInt((end >= 0) ? env_monet_version.substring(0, end) : env_monet_version);
 				} catch (NumberFormatException nfe) {
 					// ignore
 				}
@@ -2036,8 +2045,10 @@ public class MonetConnection
 	private void sendCommand(final String command, final boolean usequeryTempl) throws SQLException {
 		synchronized (server) {
 			try {
-				out.writeLine(usequeryTempl ? (queryTempl[0] + command + queryTempl[1])
-							: (commandTempl[0] + command + commandTempl[1]) );
+				if (usequeryTempl)
+					out.writeLine(queryTempl[0] + command + queryTempl[1]);
+				else
+					out.writeLine(commandTempl[0] + command + commandTempl[1]);
 				final String error = in.waitForPrompt();
 				if (error != null)
 					throw new SQLException(error.substring(6), error.substring(0, 5));
@@ -2959,7 +2970,9 @@ public class MonetConnection
 					 * then ignore this call.  If it is set to 0 we get a
 					 * prompt after the server sent it's header.
 					 */
-					int size = (cachesize == 0 ? defaultFetchSize : cachesize);
+					int size = cachesize;
+					if (size == 0)
+						size = defaultFetchSize;
 					if (maxrows > 0 && maxrows < size)
 						size = (int)maxrows;
 					// don't do work if it's not needed
@@ -2972,7 +2985,7 @@ public class MonetConnection
 					// }}} set reply size
 
 					// send query to the server
-					out.writeLine( (templ[0] == null ? "" : templ[0]) + query + (templ[1] == null ? "" : templ[1]) );
+					out.writeLine(templ[0] + query + templ[1]);
 
 					// go for new results
 					String tmpLine = in.readLine();
@@ -3013,7 +3026,7 @@ public class MonetConnection
 									res = new SchemaResponse();
 									break;
 								case StartOfHeaderParser.Q_TRANS:
-									final boolean ac = sohp.getNextAsString().equals("t") ? true : false;
+									final boolean ac = sohp.getNextAsString().equals("t");
 									if (autoCommit && ac) {
 										addWarning("Server enabled auto commit mode " +
 											"while local state already was auto commit.", "01M11");
@@ -3027,7 +3040,11 @@ public class MonetConnection
 									sohp.getNextAsInt();	// columncount
 									final int rowcount = sohp.getNextAsInt();
 									final int offset = sohp.getNextAsInt();
-									final ResultSetResponse t = (rsresponses != null) ? rsresponses.get(Integer.valueOf(id)) : null;
+									final ResultSetResponse t;
+									if (rsresponses != null)
+										t = rsresponses.get(Integer.valueOf(id));
+									else
+										t = null;
 									if (t == null) {
 										error = "M0M12!no ResultSetResponse with id " + id + " found";
 										break;
@@ -3038,11 +3055,12 @@ public class MonetConnection
 								} break;
 								} // end of switch (sohp.parse(tmpLine))
 							} catch (MCLParseException e) {
+								final int offset = e.getErrorOffset();
 								error = "M0M10!error while parsing start of header:\n" +
 									e.getMessage() +
-									" found: '" + tmpLine.charAt(e.getErrorOffset()) + "'" +
-									" in: \"" + tmpLine + "\"" +
-									" at pos: " + e.getErrorOffset();
+									" found: '" + tmpLine.charAt(offset) +
+									"' in: \"" + tmpLine +
+									"\" at pos: " + offset;
 								// flush all the rest
 								in.waitForPrompt();
 								linetype = in.getLineType();
--- a/src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java
+++ b/src/main/java/org/monetdb/jdbc/MonetDatabaseMetaData.java
@@ -2131,7 +2131,9 @@ public class MonetDatabaseMetaData
 				"WHEN c.\"type\" IN ('int','smallint','tinyint','bigint','hugeint','float','real','double','oid','wrd') THEN 2 " +
 				"ELSE 0 END AS int) AS \"NUM_PREC_RADIX\", " +
 			"cast(CASE c.\"null\" WHEN true THEN ").append(ResultSetMetaData.columnNullable)
-			.append(" WHEN false THEN ").append(ResultSetMetaData.columnNoNulls).append(" END AS int) AS \"NULLABLE\", ")
+				.append(" WHEN false THEN ").append(ResultSetMetaData.columnNoNulls)
+				.append(" ELSE ").append(ResultSetMetaData.columnNullableUnknown)
+				.append(" END AS int) AS \"NULLABLE\", ")
 			.append(useCommentsTable ? "cm.\"remark\"" : "cast(null AS varchar(9999))").append(" AS \"REMARKS\", " +
 			"c.\"default\" AS \"COLUMN_DEF\", " +
 			"cast(0 as int) AS \"SQL_DATA_TYPE\", " +
--- a/src/main/java/org/monetdb/jdbc/MonetPreparedStatement.java
+++ b/src/main/java/org/monetdb/jdbc/MonetPreparedStatement.java
@@ -80,7 +80,8 @@ public class MonetPreparedStatement
 	private final int size;
 	private final int rscolcnt;
 
-	private final String[] values;
+	private int paramCount = 0;
+	private final String[] paramValues;
 
 	/* placeholders for date/time pattern formats created once (only when needed), used multiple times */
 	/** Format of a timestamp with RFC822 time zone */
@@ -139,7 +140,6 @@ public class MonetPreparedStatement
 		schema = new String[size];
 		table = new String[size];
 		column = new String[size];
-		values = new String[size];
 
 		// fill the arrays
 		final ResultSet rs = super.getResultSet();
@@ -169,13 +169,17 @@ public class MonetPreparedStatement
 				schema[i] = rs.getString(schema_colnr);
 				table[i] = rs.getString(table_colnr);
 				column[i] = rs.getString(column_colnr);
+				// System.out.println("column " + i + " has value: " + column[i]);
 				/* when column[i] != null it is a result column of the prepared query, see getColumnIdx(int),
 				   when column[i] == null it is a parameter for the prepared statement, see getParamIdx(int). */
-				// System.out.println("column " + i + " has value: " + column[i]);
+				if (column[i] == null)
+					paramCount++;
 			}
 			rs.close();
 		}
 
+		paramValues = new String[paramCount + 1];	// parameters start from 1
+
 		// PreparedStatements are by default poolable
 		poolable = true;
 	}
@@ -212,7 +216,7 @@ public class MonetPreparedStatement
 		schema = null;
 		table = null;
 		column = null;
-		values = null;
+		paramValues = null;
 		id = -1;
 		size = -1;
 		rscolcnt = -1;
@@ -249,8 +253,8 @@ public class MonetPreparedStatement
 	 */
 	@Override
 	public void clearParameters() {
-		for (int i = 0; i < size; i++) {
-			values[i] = null;
+		for (int param = 1; param <= paramCount; param++) {
+			paramValues[param] = null;
 		}
 	}
 
@@ -785,17 +789,10 @@ public class MonetPreparedStatement
 			 * object contains information.
 			 *
 			 * @return the number of parameters
-			 * @throws SQLException if a database access error occurs
 			 */
 			@Override
-			public int getParameterCount() throws SQLException {
-				int cnt = 0;
-
-				for (int i = 0; i < size; i++) {
-					if (column[i] == null)
-						cnt++;
-				}
-				return cnt;
+			public int getParameterCount() {
+				return paramCount;
 			}
 
 			/**
@@ -809,10 +806,9 @@ public class MonetPreparedStatement
 			 *         one of ParameterMetaData.parameterNoNulls,
 			 *         ParameterMetaData.parameterNullable, or
 			 *         ParameterMetaData.parameterNullableUnknown
-			 * @throws SQLException if a database access error occurs
 			 */
 			@Override
-			public int isNullable(final int param) throws SQLException {
+			public int isNullable(final int param) {
 				return ParameterMetaData.parameterNullableUnknown;
 			}
 
@@ -962,10 +958,9 @@ public class MonetPreparedStatement
 			 *         ParameterMetaData.parameterModeOut, or
 			 *         ParameterMetaData.parameterModeInOut
 			 *         ParameterMetaData.parameterModeUnknown.
-			 * @throws SQLException if a database access error occurs
 			 */
 			@Override
-			public int getParameterMode(final int param) throws SQLException {
+			public int getParameterMode(final int param) {
 				return ParameterMetaData.parameterModeIn;
 			}
 		};
@@ -1263,7 +1258,7 @@ public class MonetPreparedStatement
 	@Override
 	public void setBytes(final int parameterIndex, final byte[] x) throws SQLException {
 		if (x == null) {
-			setNull(parameterIndex, -1);
+			setValue(parameterIndex, "NULL");
 			return;
 		}
 
@@ -1358,7 +1353,7 @@ public class MonetPreparedStatement
 	@Override
 	public void setClob(final int parameterIndex, final Clob x) throws SQLException {
 		if (x == null) {
-			setNull(parameterIndex, -1);
+			setValue(parameterIndex, "NULL");
 			return;
 		}
 
@@ -1379,7 +1374,7 @@ public class MonetPreparedStatement
 	@Override
 	public void setClob(final int parameterIndex, final Reader reader) throws SQLException {
 		if (reader == null) {
-			setNull(parameterIndex, -1);
+			setValue(parameterIndex, "NULL");
 			return;
 		}
 
@@ -1417,7 +1412,7 @@ public class MonetPreparedStatement
 	@Override
 	public void setClob(final int parameterIndex, final Reader reader, final long length) throws SQLException {
 		if (reader == null) {
-			setNull(parameterIndex, -1);
+			setValue(parameterIndex, "NULL");
 			return;
 		}
 		if (length < 0 || length > Integer.MAX_VALUE) {
@@ -1471,7 +1466,7 @@ public class MonetPreparedStatement
 		throws SQLException
 	{
 		if (x == null) {
-			setNull(parameterIndex, -1);
+			setValue(parameterIndex, "NULL");
 			return;
 		}
 
@@ -1780,7 +1775,7 @@ public class MonetPreparedStatement
 		throws SQLException
 	{
 		if (x == null) {
-			setNull(parameterIndex, -1);
+			setValue(parameterIndex, "NULL");
 			return;
 		}
 
@@ -2218,7 +2213,7 @@ public class MonetPreparedStatement
 	@Override
 	public void setString(final int parameterIndex, final String x) throws SQLException {
 		if (x == null) {
-			setNull(parameterIndex, -1);
+			setValue(parameterIndex, "NULL");
 			return;
 		}
 
@@ -2530,7 +2525,7 @@ public class MonetPreparedStatement
 		throws SQLException
 	{
 		if (x == null) {
-			setNull(parameterIndex, -1);
+			setValue(parameterIndex, "NULL");
 			return;
 		}
 
@@ -2600,7 +2595,7 @@ public class MonetPreparedStatement
 		throws SQLException
 	{
 		if (x == null) {
-			setNull(parameterIndex, -1);
+			setValue(parameterIndex, "NULL");
 			return;
 		}
 
@@ -2674,7 +2669,7 @@ public class MonetPreparedStatement
 	@Override
 	public void setURL(final int parameterIndex, final URL x) throws SQLException {
 		if (x == null) {
-			setNull(parameterIndex, -1);
+			setValue(parameterIndex, "NULL");
 			return;
 		}
 
@@ -2725,14 +2720,15 @@ public class MonetPreparedStatement
 	 */
 	@Override
 	public String toString() {
-		final StringBuilder sb = new StringBuilder(256);
+		final StringBuilder sb = new StringBuilder(128 + paramCount * 64);
 		sb.append("Prepared SQL: ").append(sqlStatement).append("\n");
 		int param = 1;
 		for (int i = 0; i < size; i++) {
 			/* when column[i] == null it is a parameter, when column[i] != null it is a result column of the prepared query */
 			if (column[i] == null) {
-				sb.append(" parameter ").append(param++).append(" ").append(monetdbType[i]);
-				sb.append(", set value: ").append((values[i] != null) ? values[i] : "<null>").append("\n");
+				sb.append(" parameter ").append(param).append(" ").append(monetdbType[i]);
+				sb.append(", set value: ").append((paramValues[param] != null) ? paramValues[param] : "<null>").append("\n");
+				param++;
 			}
 		}
 		return sb.toString();
@@ -2788,37 +2784,41 @@ public class MonetPreparedStatement
 	 * @throws SQLException if the given index is out of bounds
 	 */
 	private final void setValue(final int parameterIndex, final String val) throws SQLException {
-		values[getParamIdx(parameterIndex)] = (val == null ? "NULL" : val);
+		if (parameterIndex < 1 || parameterIndex > paramCount)
+			throw new SQLException("No such parameter with index: " + parameterIndex, "M1M05");
+
+		if (val != null)
+			paramValues[parameterIndex] = val;
+		else
+			paramValues[parameterIndex] = "NULL";
 	}
 
 	/**
-	 * Transforms the prepare query into a simple SQL query by replacing
-	 * the ?'s with the given column contents.
-	 * Mind that the JDBC specs allow `reuse' of a value for a column over
-	 * multiple executes.
+	 * Constructs an "exec ##(paramval, ...)" statement string for the current parameter values.
+	 * Mind that the JDBC specs allow 'reuse' of a value for a parameter over multiple executes.
 	 *
-	 * @return the simple SQL string for the prepare query
-	 * @throws SQLException if not all columns are set
+	 * @return the "exec ##(...)" string
+	 * @throws SQLException if not all parameters are set with a value
 	 */
+	private StringBuilder execStmt;	// created once, re-used multiple times so much less objects are created and gc-ed
 	private final String transform() throws SQLException {
-		final StringBuilder buf = new StringBuilder(8 + 12 * size);
-		buf.append("exec ").append(id).append('(');
-		// check if all columns are set and do a replace
-		int col = 0;
-		for (int i = 0; i < size; i++) {
-			if (column[i] != null)
-				continue;
-			col++;
-			if (col > 1)
-				buf.append(',');
-			if (values[i] == null)
-				throw new SQLException("Cannot execute, parameter " + col + " is missing.", "M1M05");
+		if (execStmt == null)
+			// first time use, create it once
+			execStmt = new StringBuilder(32 + paramCount * 32);
+		else
+			execStmt.setLength(0);	// clear the buffer
 
-			buf.append(values[i]);
+		execStmt.append("exec ").append(id).append('(');
+		// check if all parameters are set and add the parameter values
+		for (int param = 1; param <= paramCount; param++) {
+			if (paramValues[param] == null)
+				throw new SQLException("Cannot execute, parameter " + param + " is missing.", "M1M05");
+			if (param > 1)
+				execStmt.append(',');
+			execStmt.append(paramValues[param]);
 		}
-		buf.append(')');
-
-		return buf.toString();
+		execStmt.append(')');
+		return execStmt.toString();
 	}
 
 	/**
--- a/src/main/java/org/monetdb/jdbc/MonetResultSet.java
+++ b/src/main/java/org/monetdb/jdbc/MonetResultSet.java
@@ -263,7 +263,10 @@ public class MonetResultSet
 		// store it
 		curRow = row;
 
-		final String tmpLine = (header != null) ? header.getLine(row - 1) : null;
+		if (header == null)
+			return false;
+
+		final String tmpLine = header.getLine(row - 1);
 		if (tmpLine == null)
 			return false;
 
@@ -434,7 +437,7 @@ public class MonetResultSet
 						return null;
 					return new java.io.ByteArrayInputStream(bte);
 			}
-			throw new SQLException("Cannot operate on " + types[columnIndex - 1] + " type", "M1M05");
+			throw new SQLException("Cannot operate on type: " + types[columnIndex - 1], "M1M05");
 		} catch (IndexOutOfBoundsException e) {
 			throw newSQLInvalidColumnIndexException(columnIndex);
 		}
@@ -904,7 +907,7 @@ public class MonetResultSet
 				case Types.LONGVARBINARY:
 					return MonetBlob.hexStrToByteArray(val);
 				default:
-					throw new SQLException("Cannot operate on " + types[columnIndex - 1] + " type", "M1M05");
+					throw new SQLException("Cannot operate on type: " + types[columnIndex - 1], "M1M05");
 			}
 		} catch (NumberFormatException e) {
 			throw newSQLNumberFormatException(e);
@@ -971,8 +974,8 @@ public class MonetResultSet
 	 */
 	@Override
 	public String getCursorName() throws SQLException {
-		throw new SQLException("Positioned updates not supported for this " +
-				   "cursor (" + (header != null ? header.id : "") + ")", "0AM21");
+		throw new SQLException("Positioned updates not supported for this cursor ("
+				+ (header != null ? header.id + ")" : ")"), "0AM21");
 	}
 
 	/**
@@ -1052,7 +1055,7 @@ public class MonetResultSet
 			break;
 		case ResultSet.FETCH_REVERSE:
 		case ResultSet.FETCH_UNKNOWN:
-			throw new SQLException("Not supported direction " + direction, "0A000");
+			throw new SQLException("Not supported direction: " + direction, "0A000");
 		default:
 			throw new SQLException("Illegal direction: " + direction, "M1M05");
 		}
@@ -1250,13 +1253,16 @@ public class MonetResultSet
 			private final String[] schemas = (header != null) ? header.getSchemaNames() : null;
 			private final String[] tables = (header != null) ? header.getTableNames() : null;
 			private final MonetConnection conn = (MonetConnection)getStatement().getConnection();
-			// for the more expensive methods (getPrecision(), getScale(), isNullable(), isAutoIncrement()), we
-			// use caches to store precision, scale and isNullable values from getColumnInfo() combined per fully qualified column.
+
+			// for the methods: getPrecision(), getScale(), isNullable() and isAutoIncrement(), we use
+			// caches to store precision, scale, isNullable and isAutoincrement values for each resultset column
+			// so they do not need to queried and fetched from the server again and again.
 			private final int array_size = columns.length + 1;  // add 1 as in JDBC columns start from 1 (array from 0).
+			private final boolean[] _is_queried = new boolean[array_size];
 			private final boolean[] _is_fetched = new boolean[array_size];
 			private final int[] _precision	= new int[array_size];
 			private final int[] _scale	= new int[array_size];
-			private final int[] _isNullable	= new int[array_size];
+			private final int[] _isNullable = new int[array_size];
 			private final boolean[] _isAutoincrement = new boolean[array_size];
 
 			/**
@@ -1269,81 +1275,161 @@ public class MonetResultSet
 			}
 
 			/**
-			 * A private method to fetch the precision, scale, isNullable and isAutoincrement value for a fully qualified column.
-			 * As getColumnInfo() is an expensive method we call it only once per column and store
-			 * the precision, scale, isNullable and isAutoincrement values in the above array caches.
-			 * Also we only call getColumnInfo() when we have a non empty schema name and table name and column name.
+			 * A private method to fetch the precision, scale, isNullable and isAutoincrement values
+			 * combined for a specific column.
+			 * The fetched values are stored in the above array caches.
 			 */
-			private final void fetchColumnInfo(final int column) throws SQLException
-			{
+			private final void fetchColumnInfo(final int column) throws SQLException {
+				// for debug: System.out.println("fetchColumnInfo(" + column + ")");
 				checkColumnIndexValidity(column);
 
-				_is_fetched[column] = true;
+				if (_is_fetched[column] != true) {
+					// fetch column info for multiple columns combined in one go, starting at 1
+					fetchManyColumnsInfo(1);
+					if (_is_fetched[column] != true) {
+						// fetch info for column x if it was not fetched by the previous call
+						fetchManyColumnsInfo(column);
+					}
+				}
+
+				if (_is_fetched[column])
+					return;
+
+				// apparently no data could be fetched for this resultset column, fall back to defaults
 				_precision[column] = 0;
 				_scale[column] = 0;
 				_isNullable[column] = columnNullableUnknown;
 				_isAutoincrement[column] = false;
-
-				// we will only call getColumnInfo() when we have a specific schema name, table name and column name
-				final String schName = getSchemaName(column);
-				if (schName != null && !schName.isEmpty()) {
-					final String tblName = getTableName(column);
-					if (tblName != null && !tblName.isEmpty()) {
-						final String colName = getColumnName(column);
-						if (colName != null && !colName.isEmpty()) {
-							// for precision, scale, isNullable and isAutoincrement we query the information from data dictionary
-							final ResultSet colInfo = getColumnInfo(schName, tblName, colName);
-							if (colInfo != null) {
-								// we expect exactly one row in the resultset
-								if (colInfo.next()) {
-									_precision[column] = colInfo.getInt(1);  // col 1 (was 7) is "COLUMN_SIZE"
-									_scale[column] = colInfo.getInt(2);  // col 2 (was 9) is "DECIMAL_DIGITS"
-									_isNullable[column] = colInfo.getInt(3);  // col 3 (was 11) is "NULLABLE"
-									_isAutoincrement[column] = colInfo.getBoolean(4);  // col 4 (was 23) is "IS_AUTOINCREMENT"
+			}
+
+			/**
+			 * A private method to fetch the precision, scale, isNullable and isAutoincrement values
+			 * for many fully qualified columns combined in one SQL query to reduce the number of queries sent.
+			 * As fetching this meta information from the server per column is costly we combine the querying of
+			 * the precision, scale, isNullable and isAutoincrement values and cache it in internal arrays.
+			 * We also do this for many (up to 50) columns combined in one query to reduce
+			 * the number of queries needed for fetching this metadata for all resultset columns.
+			 * Many generic JDBC database tools (e.g. SQuirreL) request this meta data for each column of each resultset,
+			 * so these optimisations reduces the number of meta data queries significantly.
+			 */
+			private final void fetchManyColumnsInfo(final int column) throws SQLException {
+				// for debug: System.out.println("fetchManyColumnsInfo(" + column + ")");
+
+				// Most queries have less than 50 resultset columns
+				// So 50 is a good balance between speedup (up to 49x) and size of query sent to server
+				final int MAX_COLUMNS_PER_QUERY = 50;
+
+				final StringBuilder query = new StringBuilder(600 + (MAX_COLUMNS_PER_QUERY * 150));
+				/* next SQL query is a simplified version of query in MonetDatabaseMetaData.getColumns(), to fetch only the needed attributes of a column */
+				query.append("SELECT " +
+					"s.\"name\" AS schnm, " +
+					"t.\"name\" AS tblnm, " +
+					"c.\"name\" AS colnm, " +
+					"c.\"type_digits\", " +
+					"c.\"type_scale\", " +
+					"cast(CASE c.\"null\" WHEN true THEN ").append(ResultSetMetaData.columnNullable)
+						.append(" WHEN false THEN ").append(ResultSetMetaData.columnNoNulls)
+						.append(" ELSE ").append(ResultSetMetaData.columnNullableUnknown)
+						.append(" END AS int) AS nullable, ").append(
+					"cast(CASE WHEN c.\"default\" IS NOT NULL AND c.\"default\" LIKE 'next value for %' THEN true ELSE false END AS boolean) AS isautoincrement " +
+				"FROM \"sys\".\"columns\" c " +
+				"JOIN \"sys\".\"tables\" t ON c.\"table_id\" = t.\"id\" " +
+				"JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" " +
+				"WHERE ");
+
+				/* combine the conditions for multiple (up to 50) columns into the WHERE-clause */
+				String schName = null;
+				String tblName = null;
+				String colName = null;
+				int queriedcolcount = 0;
+				for (int col = column; col < array_size && queriedcolcount < MAX_COLUMNS_PER_QUERY; col++) {
+					if (_is_fetched[col] != true) {
+						if (_is_queried[col] != true) {
+							_precision[col] = 0;
+							_scale[col] = 0;
+							_isNullable[col] = columnNullableUnknown;
+							_isAutoincrement[col] = false;
+							schName = getSchemaName(col);
+							if (schName != null && !schName.isEmpty()) {
+								tblName = getTableName(col);
+								if (tblName != null && !tblName.isEmpty()) {
+									colName = getColumnName(col);
+									if (colName != null && !colName.isEmpty()) {
+										if (queriedcolcount > 0)
+											query.append(" OR ");
+										query.append("(s.\"name\" = ").append(MonetWrapper.sq(schName));
+										query.append(" AND t.\"name\" = ").append(MonetWrapper.sq(tblName));
+										query.append(" AND c.\"name\" = ").append(MonetWrapper.sq(colName));
+										query.append(")");
+										_is_queried[col] = true;	// flag it
+										queriedcolcount++;
+									}
 								}
-								colInfo.close();  // close the resultset to release resources
+							}
+							if (_is_queried[col] != true) {
+								// make sure we do not try to query it again next time as it is not queryable
+								_is_fetched[col] = true;
 							}
 						}
 					}
 				}
-			}
-
-			/* private simplified copy of MonetDatabaseMetaData.getColumns() method to fetch only 4 needed attributes of a specific column */
-			private final ResultSet getColumnInfo(final String schemaName, final String tableName, final String columnName) throws SQLException
-			{
-				final StringBuilder query = new StringBuilder(700);
-				query.append("SELECT " +
-					"c.\"type_digits\" AS \"COLUMN_SIZE\", " +
-					"c.\"type_scale\" AS \"DECIMAL_DIGITS\", " +
-					"cast(CASE c.\"null\" WHEN true THEN ").append(ResultSetMetaData.columnNullable)
-						.append(" WHEN false THEN ").append(ResultSetMetaData.columnNoNulls)
-						.append(" ELSE ").append(columnNullableUnknown)
-						.append(" END AS int) AS \"NULLABLE\", ").append(
-					"cast(CASE WHEN c.\"default\" IS NOT NULL AND c.\"default\" LIKE 'next value for %' THEN true ELSE false END AS boolean) AS \"IS_AUTOINCREMENT\" " +
-					// ", s.\"name\" AS \"TABLE_SCHEM\", t.\"name\" AS \"TABLE_NAME\", c.\"name\" AS \"COLUMN_NAME\" " +
-				"FROM \"sys\".\"columns\" c " +
-				"JOIN \"sys\".\"tables\" t ON c.\"table_id\" = t.\"id\" " +
-				"JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" ");
-
-				query.append("WHERE s.\"name\" = ").append(MonetWrapper.sq(schemaName));
-				query.append(" AND t.\"name\" = ").append(MonetWrapper.sq(tableName));
-				query.append(" AND c.\"name\" = ").append(MonetWrapper.sq(columnName));
-				// query.append(" ORDER BY \"TABLE_SCHEM\", \"TABLE_NAME\"");
-
-				ResultSet rs = null;
+
+				if (queriedcolcount == 0)
+					return;
+
+				// execute query to get information on queriedcolcount (or less) columns.
 				final Statement stmt = conn.createStatement();
 				if (stmt != null) {
 					// for debug: System.out.println("SQL (len " + query.length() + "): " + query.toString());
-					rs = stmt.executeQuery(query.toString());
+					final ResultSet rs = stmt.executeQuery(query.toString());
 					if (rs != null) {
-						/* we want the statement object to be closed also when the resultset is closed by the caller */
-						stmt.closeOnCompletion();
-					} else {
-						/* failed to produce a resultset, so release resources for created statement object now */
-						stmt.close();
+						String rsSchema = null;
+						String rsTable = null;
+						String rsColumn = null;
+						while (rs.next()) {
+							rsSchema = rs.getString(1);	// col 1 is schnm
+							rsTable = rs.getString(2);	// col 2 is tblnm
+							rsColumn = rs.getString(3);	// col 3 is colnm
+							// find the matching schema.table.column entry in the array
+							for (int col = 1; col < array_size; col++) {
+								if (_is_fetched[col] != true && _is_queried[col]) {
+									colName = getColumnName(col);
+									if (colName != null && colName.equals(rsColumn)) {
+										tblName = getTableName(col);
+										if (tblName != null && tblName.equals(rsTable)) {
+											schName = getSchemaName(col);
+											if (schName != null && schName.equals(rsSchema)) {
+												// found matching entry
+												// for debug: System.out.println("Found match at [" + col + "] for " + schName + "." + tblName + "." + colName);
+												_precision[col] = rs.getInt(4);	// col 4 is "type_digits" (or "COLUMN_SIZE")
+												_scale[col] = rs.getInt(5);		// col 5 is "type_scale" (or "DECIMAL_DIGITS")
+												_isNullable[col] = rs.getInt(6);	// col 6 is nullable (or "NULLABLE")
+												_isAutoincrement[col] = rs.getBoolean(7); // col 7 is isautoincrement (or "IS_AUTOINCREMENT")
+												_is_fetched[col] = true;
+												queriedcolcount--;
+												// we found the match, exit the for-loop
+												col = array_size;
+											}
+										}
+									}
+								}
+							}
+						}
+						rs.close();
+					}
+					stmt.close();
+				}
+
+				if (queriedcolcount != 0) {
+					// not all queried columns have resulted in a returned data row.
+					// make sure we do not match those columns again next run
+					for (int col = column; col < array_size; col++) {
+						if (_is_fetched[col] != true && _is_queried[col]) {
+							_is_fetched[col] = true;
+							// for debug: System.out.println("Found NO match at [" + col + "] for " + getSchemaName(col) + "." + getTableName(col) + "." + getColumnName(col));
+						}
 					}
 				}
-				return rs;
 			}
 
 			/**
@@ -2735,19 +2821,18 @@ public class MonetResultSet
 		}
 		if (pdate == null) {
 			// parsing failed
-			final String errMsg;
+			final StringBuilder errMsg = new StringBuilder(128);
 			final int epos = ppos.getErrorIndex();
 			if (epos == -1) {
-				errMsg = "parsing '" + monetDateStr + "' failed";
+				errMsg.append("parsing '").append(monetDateStr).append("' failed");
 			} else if (epos < monetDate.length()) {
-				errMsg = "parsing failed," +
-					 " found: '" + monetDate.charAt(epos) + "'" +
-					 " in: \"" + monetDateStr + "\"" +
-					 " at pos: " + (epos + (negativeYear ? 2 : 1));
+				errMsg.append("parsing failed at pos ").append(epos + (negativeYear ? 2 : 1))
+					.append(" found: '").append(monetDate.charAt(epos))
+					.append("' in '").append(monetDateStr).append("'");
 			} else {
-				errMsg = "parsing failed, expected more data after '" +	monetDateStr + "'";
+				errMsg.append("parsing failed, expected more data after '").append(monetDateStr).append("'");
 			}
-			throw new SQLException(errMsg, "01M10");
+			throw new SQLException(errMsg.toString(), "01M10");
 		}
 
 		cal.setTime(pdate);
@@ -2788,10 +2873,11 @@ public class MonetResultSet
 					while (ctr++ < 9)
 						nanos *= 10;
 				} catch (MCLParseException e) {
+					final int offset = e.getErrorOffset();
 					addWarning(e.getMessage() +
-							" found: '" + monDate[e.getErrorOffset()] + "'" +
-							" in: \"" + monetDate + "\"" +
-							" at pos: " + e.getErrorOffset(), "01M10");
+							" found: '" + monDate[offset] +
+							"' in: \"" + monetDate +
+							"\" at pos: " + offset, "01M10");
 					// default value
 					nanos = 0;
 				}
@@ -2874,8 +2960,9 @@ public class MonetResultSet
 				}
 				cal = Calendar.getInstance();
 			}
-			final int ret = getJavaDate(cal, columnIndex, Types.DATE);
-			return ret == -1 ? null : new java.sql.Date(cal.getTimeInMillis());
+			if (getJavaDate(cal, columnIndex, Types.DATE) == -1)
+				return null;
+			return new java.sql.Date(cal.getTimeInMillis());
 		} catch (IndexOutOfBoundsException e) {
 			throw newSQLInvalidColumnIndexException(columnIndex);
 		}
@@ -2963,8 +3050,9 @@ public class MonetResultSet
 				}
 				cal = Calendar.getInstance();
 			}
-			final int ret = getJavaDate(cal, columnIndex, Types.TIME);
-			return ret == -1 ? null : new Time(cal.getTimeInMillis());
+			if (getJavaDate(cal, columnIndex, Types.TIME) == -1)
+				return null;
+			return new Time(cal.getTimeInMillis());
 		} catch (IndexOutOfBoundsException e) {
 			throw newSQLInvalidColumnIndexException(columnIndex);
 		}
--- a/src/main/java/org/monetdb/jdbc/MonetStatement.java
+++ b/src/main/java/org/monetdb/jdbc/MonetStatement.java
@@ -206,7 +206,10 @@ public class MonetStatement
 		// 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];
+			if (ret[i] >= Integer.MAX_VALUE)
+				counts[i] = Integer.MAX_VALUE;
+			else
+				counts[i] = (int)ret[i];
 		}
 		return counts;
 	}
@@ -776,10 +779,9 @@ public class MonetStatement
 	 */
 	@Override
 	public ResultSet getResultSet() throws SQLException {
-		return (header != null && header instanceof MonetConnection.ResultSetResponse)
-			? new MonetResultSet(this,
-					(MonetConnection.ResultSetResponse)header)
-			: null;
+		if (header != null && header instanceof MonetConnection.ResultSetResponse)
+			return new MonetResultSet(this, (MonetConnection.ResultSetResponse)header);
+		return null;
 	}
 
 	/**
@@ -1111,17 +1113,16 @@ public class MonetStatement
 	 * @throws SQLException if a database access error occurs or this
 	 *	method is called on a closed Statement
 	 */
+	@Override
 	public long getLargeUpdateCount() throws SQLException {
-		long ret = -1;
 		if (header != null) {
 			if (header instanceof MonetConnection.UpdateResponse) {
-				ret = ((MonetConnection.UpdateResponse)header).count;
+				return ((MonetConnection.UpdateResponse)header).count;
 			} else if (header instanceof MonetConnection.SchemaResponse) {
-				ret = ((MonetConnection.SchemaResponse)header).state;
+				return ((MonetConnection.SchemaResponse)header).state;
 			}
 		}
-
-		return ret;
+		return -1;
 	}
 
 	/**
@@ -1570,9 +1571,7 @@ final class MonetVirtualResultSet extend
 	 */
 	@Override
 	public void close() {
-		if (!closed) {
-			closed = true;
-			// types and columns are MonetResultSets private parts
-		}
+		closed = true;
+		// types and columns are MonetResultSets private parts
 	}
 }
--- a/src/main/java/org/monetdb/util/CmdLineOpts.java
+++ b/src/main/java/org/monetdb/util/CmdLineOpts.java
@@ -39,7 +39,7 @@ public final class CmdLineOpts {
 			final String defaulta,
 			final String descriptiona)
 		throws OptionsException {
-		OptionContainer oc =
+		final OptionContainer oc =
 			new OptionContainer(
 				shorta,
 				longa,
@@ -54,7 +54,7 @@ public final class CmdLineOpts {
 	}
 
 	public void removeOption(final String name) {
-		OptionContainer oc = opts.get(name);
+		final OptionContainer oc = opts.get(name);
 		if (oc != null) {
 			opts.remove(oc.shorta);
 			opts.remove(oc.longa);
@@ -62,7 +62,7 @@ public final class CmdLineOpts {
 	}
 
 	public OptionContainer getOption(final String key) throws OptionsException {
-		OptionContainer ret = opts.get(key);
+		final OptionContainer ret = opts.get(key);
 		if (ret == null)
 			throw new OptionsException("No such option: " + key);
 
@@ -80,9 +80,11 @@ public final class CmdLineOpts {
 				in.close();
 			}
 
+			String key;
+			OptionContainer option = null;
 			for (java.util.Enumeration<?> e = prop.propertyNames(); e.hasMoreElements(); ) {
-				String key = (String) e.nextElement();
-				OptionContainer option = opts.get(key);
+				key = (String) e.nextElement();
+				option = opts.get(key);
 				if (option == null)
 					throw new OptionsException("Unknown option: " + key);
 				option.resetArguments();
@@ -346,9 +348,8 @@ public final class CmdLineOpts {
 		public void addArgument(final String val) throws OptionsException {
 			if (cardinality == CAR_ZERO) {
 				throw new OptionsException("option " + name + " does not allow arguments");
-			} else if ((cardinality == CAR_ONE ||
-					cardinality == CAR_ZERO_ONE) &&
-					values.size() >= 1) {
+			}
+			if ((cardinality == CAR_ONE || cardinality == CAR_ZERO_ONE) && values.size() >= 1) {
 				throw new OptionsException("option " + name + " does at max allow only one argument");
 			}
 			// we can add it
--- a/src/main/java/org/monetdb/util/MDBvalidator.java
+++ b/src/main/java/org/monetdb/util/MDBvalidator.java
@@ -1123,11 +1123,10 @@ public final class MDBvalidator {
 		{"querylog_history", "id", "id", "querylog_catalog", null},
 		{"querylog_history", "owner", "name", "users", null},
 		{"querylog_history", "pipe", "name", "optimizers", null},
-		{"queue WHERE tag > cast(0 as oid) AND ", "tag", "tag", "queue", null},
-		{"queue WHERE tag > cast(0 as oid) AND ", "tag", "cast(tag as oid)", "queue", null},
-		{"queue", "tag", "cast(tag as oid)", "queue", null},
+// not a fk:	{"queue", "sessionid", "sessionid", "sessions", "37"},	// as queue contains a historical list, the session may have been closed in the meantime, so not a real persistent fk
 // not a fk:	{"queue", "\"username\"", "name", "users", null},	// as queue contains a historical list, the user may have been removed in the meantime, so not a real persistent fk
 		{"sessions", "\"username\"", "name", "users", null},
+		{"sessions", "optimizer", "name", "optimizers", "37"},
 		{"statistics", "column_id", "id", "(SELECT id FROM sys._columns UNION ALL SELECT id FROM tmp._columns) as c", null},
 		{"statistics", "type", "sqlname", "types", null},
 		{"storage()", "schema", "name", "schemas", null},
--- a/tests/JDBC_API_Tester.java
+++ b/tests/JDBC_API_Tester.java
@@ -32,7 +32,7 @@ import org.monetdb.jdbc.types.URL;
  * to only one time instead of 40+ 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().
+ * Only when it deviates the output is sent to system err, see compareExpectedOutput().
  *
  * @author Martin van Dinther
  * @version 0.2
@@ -41,6 +41,7 @@ final public class JDBC_API_Tester {
 	StringBuilder sb;	// buffer to collect the test output
 	final static int sbInitLen = 3712;
 	Connection con;	// main connection shared by all tests
+	boolean foundDifferences = false;
 
 	public static void main(String[] args) throws Exception {
 		String con_URL = args[0];
@@ -96,8 +97,13 @@ final public class JDBC_API_Tester {
 		jt.BugResultSetMetaData_Bug_6183();
 		jt.BugSetQueryTimeout_Bug_3357();
 		jt.SQLcopyinto();
+		/* run next long running test (11 minutes) only before a new release */
+	/*	jt.Test_PSlargeamount(); */
 
 		jt.closeConx(jt.con);
+
+		if (jt.foundDifferences)
+			System.exit(-1);
 	}
 
 	private void Test_Cautocommit(String arg0) {
@@ -823,10 +829,13 @@ final public class JDBC_API_Tester {
 			stmt = con.createStatement();
 			// query sys.types to find out if sql datatype hugeint is supported
 			rs = stmt.executeQuery("SELECT sqlname from sys.types where sqlname = 'hugeint';");
-			if (rs != null && rs.next()) {
-				String sqlname = rs.getString(1);
-				if ("hugeint".equals(sqlname))
-					supportsHugeInt = true;
+			if (rs != null) {
+				if (rs.next()) {
+					if ("hugeint".equals(rs.getString(1)))
+						supportsHugeInt = true;
+				}
+				rs.close();
+				rs = null;
 			}
 		} catch (SQLException e) {
 			sb.append("FAILED: ").append(e.getMessage()).append("\n");
@@ -1525,6 +1534,90 @@ final public class JDBC_API_Tester {
 			"100, true\n");
 	}
 
+	/* Create a lot of PreparedStatements, to emulate webloads such as those from Hibernate. */
+	/* this test is same as Test_PSsomeamount() but for many more PreparedStatements to stress the server */
+	private void Test_PSlargeamount() {
+		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\n");
+			for (int i = 0; i < 50001; i++) {
+				pstmt = con.prepareStatement("select " + i + ", " + i + " = ?");
+				pstmt.setInt(1, i);
+				rs = pstmt.executeQuery();
+				if (rs.next() && i % 1000 == 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_PSlargeamount",
+			"0. true	true\n" +
+			"1. Preparing and executing a unique statement\n" +
+			"0, true\n" +
+			"1000, true\n" +
+			"2000, true\n" +
+			"3000, true\n" +
+			"4000, true\n" +
+			"5000, true\n" +
+			"6000, true\n" +
+			"7000, true\n" +
+			"8000, true\n" +
+			"9000, true\n" +
+			"10000, true\n" +
+			"11000, true\n" +
+			"12000, true\n" +
+			"13000, true\n" +
+			"14000, true\n" +
+			"15000, true\n" +
+			"16000, true\n" +
+			"17000, true\n" +
+			"18000, true\n" +
+			"19000, true\n" +
+			"20000, true\n" +
+			"21000, true\n" +
+			"22000, true\n" +
+			"23000, true\n" +
+			"24000, true\n" +
+			"25000, true\n" +
+			"26000, true\n" +
+			"27000, true\n" +
+			"28000, true\n" +
+			"29000, true\n" +
+			"30000, true\n" +
+			"31000, true\n" +
+			"32000, true\n" +
+			"33000, true\n" +
+			"34000, true\n" +
+			"35000, true\n" +
+			"36000, true\n" +
+			"37000, true\n" +
+			"38000, true\n" +
+			"39000, true\n" +
+			"40000, true\n" +
+			"41000, true\n" +
+			"42000, true\n" +
+			"43000, true\n" +
+			"44000, true\n" +
+			"45000, true\n" +
+			"46000, true\n" +
+			"47000, true\n" +
+			"48000, true\n" +
+			"49000, true\n" +
+			"50000, true\n");
+	}
+
 	private void Test_PSsqldata() {
 		sb.setLength(0);	// clear the output log buffer
 
@@ -1811,36 +1904,37 @@ final public class JDBC_API_Tester {
 			// 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
+			// 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");
+				sb.append("retrieved row (String):\n")
+				.append(rs.getString("ts")).append(" | ")
+			//	.append(rs.getString("tsz")).append(" | ")	-- this values changes when summer or wintertime changes so no stable output
+				.append(rs.getString("t")).append(" | ")
+			//	.append(rs.getString("tz"))	-- this values changes when summer or wintertime changes so no stable output
+				.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");
+				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");
+				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");
+				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) {
@@ -1872,7 +1966,8 @@ final public class JDBC_API_Tester {
 			"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" +
+// old output		"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" +
+			"1970-01-01 00:00:00.000000 | 00:00: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" +
@@ -1880,7 +1975,8 @@ final public class JDBC_API_Tester {
 			"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" +
+// old output		"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" +
+			"1970-01-01 00:00:00.000000 | 00:00: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" +
@@ -1888,7 +1984,8 @@ final public class JDBC_API_Tester {
 			"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" +
+// old output		"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" +
+			"1969-12-31 16:00:00.000000 | 16:00: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" +
@@ -1896,7 +1993,8 @@ final public class JDBC_API_Tester {
 			"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" +
+// old output		"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" +
+			"1970-01-01 00:00:00.000000 | 00:00: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" +
@@ -2754,13 +2852,13 @@ final public class JDBC_API_Tester {
 			"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" +
-			"4. vc 2004-04-24 11:43:53.654321 to tm: 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 tm: rs.getTime(colnm) failed with error: parsing failed at pos 5 found: '-' in '2004-04-24 11:43:53.654321'\n" +
 			"4. vc 2004-04-24 11:43:53.654321 to dt: 2004-04-24\n" +
-			"5. vc 11:43:53 to ts: rs.getTimestamp(colnm) failed with error: parsing failed, found: ':' in: \"11:43:53\" at pos: 3\n" +
+			"5. vc 11:43:53 to ts: rs.getTimestamp(colnm) failed with error: parsing failed at pos 3 found: ':' in '11:43:53'\n" +
 			"5. vc 11:43:53 to tm: 11:43:53\n" +
-			"5. vc 11:43:53 to dt: rs.getDate(colnm) failed with error: parsing failed, found: ':' in: \"11:43:53\" at pos: 3\n" +
+			"5. vc 11:43:53 to dt: rs.getDate(colnm) failed with error: parsing failed at pos 3 found: ':' in '11:43:53'\n" +
 			"6. vc 2004-04-24 to ts: 2004-04-24 00:00:00.0\n" +
-			"6. vc 2004-04-24 to tm: rs.getTime(colnm) failed with error: parsing failed, found: '-' in: \"2004-04-24\" at pos: 5\n" +
+			"6. vc 2004-04-24 to tm: rs.getTime(colnm) failed with error: parsing failed at pos 5 found: '-' in '2004-04-24'\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" +
@@ -2781,13 +2879,13 @@ final public class JDBC_API_Tester {
 			"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" +
-			"17. vc 904-04-24 11:43:53.567 to tm: 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 tm: rs.getTime(colnm) failed with error: parsing failed at pos 4 found: '-' in '904-04-24 11:43:53.567'\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" +
-			"18. vc 74-04-24 11:43:53.567 to tm: 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 tm: rs.getTime(colnm) failed with error: parsing failed at pos 3 found: '-' in '74-04-24 11:43:53.567'\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" +
-			"19. vc 4-04-24 11:43:53.567 to tm: 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 tm: rs.getTime(colnm) failed with error: parsing failed at pos 2 found: '-' in '4-04-24 11:43:53.567'\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" +
@@ -2802,10 +2900,10 @@ final public class JDBC_API_Tester {
 			"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" +
-			"25. vc -2004-04-24 11:43:53.654321 to tm: 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 tm: rs.getTime(colnm) failed with error: parsing failed at pos 6 found: '-' in '-2004-04-24 11:43:53.654321'\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" +
-			"26. vc -3004-04-24 to tm: rs.getTime(colnm) failed with error: parsing failed, found: '-' in: \"-3004-04-24\" at pos: 6\n" +
+			"26. vc -3004-04-24 to tm: rs.getTime(colnm) failed with error: parsing failed at pos 6 found: '-' in '-3004-04-24'\n" +
 			"26. vc -3004-04-24 to dt: 3004-04-24\n" +
 			"0. true	true\n");
 	}
@@ -4028,7 +4126,7 @@ final public class JDBC_API_Tester {
 				"Script size is 83256\n" +
 				"First test repeat 10 times. Iteration: 1 2 3 4 5 6 7 8 9 10 \n" +
 				"Completed first test\n" +
-				"Second test repeat 10 times. Iteration: 1 2 3 4 5 6 7 8 9 10 \n" + 
+				"Second test repeat 10 times. Iteration: 1 2 3 4 5 6 7 8 9 10 \n" +
 				"Completed second test\n" +
 				"Script size is 3012\n" +
 				"Third test repeat 9 times.\n" +
@@ -5043,19 +5141,20 @@ final public class JDBC_API_Tester {
 
 	private void compareExpectedOutput(String testname, String expected) {
 		if (!expected.equals(sb.toString())) {
-			System.out.print("Test '");
-			System.out.print(testname);
+			foundDifferences = true;
+			System.err.print("Test '");
+			System.err.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();
+				System.err.print("()");
+			System.err.println("' produced different output!");
+			System.err.println("Expected:");
+			System.err.println(expected);
+			System.err.println("Gotten:");
+			System.err.println(sb);
+			System.err.println();
 		}
 		if (sb.length() > sbInitLen) {
-			System.out.println("Test '" + testname
+			System.err.println("Test '" + testname
 				+ "' produced output > " + sbInitLen
 				+ " chars! Enlarge sbInitLen to: " + sb.length());
 		}
--- a/tests/SQLcopyinto.java
+++ b/tests/SQLcopyinto.java
@@ -28,7 +28,7 @@ public class SQLcopyinto {
 		System.out.println("SQLcopyinto started");
 		if (args.length == 0) {
 			System.err.println("Error: missing startup argument: the jdbc connection url !");
-			System.err.println("Usage: java -cp monetdb-jdbc-2.29.jre7.jar:. SQLcopyinto \"jdbc:monetdb://localhost:50000/demo?user=monetdb&password=monetdb\"");
+			System.err.println("Usage: java -cp monetdb-jdbc-3.0.jre8.jar:. SQLcopyinto \"jdbc:monetdb://localhost:50000/demo?user=monetdb&password=monetdb\"");
 			System.exit(-1);
 		}
 		String jdbc_url = args[0];
@@ -118,14 +118,13 @@ public class SQLcopyinto {
 				mclOut.write("" + i + ",val_" + i);
 				mclOut.newLine();
 			}
+
 			mclOut.writeLine(""); // need this one for synchronisation over flush()
-
 			error = mclIn.waitForPrompt();
 			if (error != null)
 				System.err.println("Received error: " + error);
 
 			mclOut.writeLine(""); // need this one for synchronisation over flush()
-
 			error = mclIn.waitForPrompt();
 			if (error != null)
 				System.err.println("Received finish error: " + error);
@@ -134,7 +133,7 @@ public class SQLcopyinto {
 		} catch (Exception e) {
 			System.err.println("Mapi Exception: " + e.getMessage());
 		} finally {
-			// close connection to MonetDB server
+			// close MapiSocket connection to MonetDB server
 			server.close();
 		}
 
deleted file mode 100644
--- a/tests/Test_PSlargeamount.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.*;
-
-/* Create a lot of PreparedStatements, to emulate webloads such as those
- * from Hibernate. */
-
-public class Test_PSlargeamount {
-	public static void main(String[] args) throws Exception {
-		Connection con = DriverManager.getConnection(args[0]);
-		Statement stmt = con.createStatement();
-		PreparedStatement pstmt;
-
-		// >> true: auto commit should be on
-		System.out.println("0. true\t" + con.getAutoCommit());
-
-		try {
-			System.out.println("1. Preparing and executing a unique statement");
-			for (int i = 0; i < 50000; i++) {
-				pstmt = con.prepareStatement("select " + i + ", " + i + " = ?");
-				pstmt.setInt(1, i);
-				ResultSet rs = pstmt.executeQuery();
-				if (rs.next() && i % 1000 == 0) {
-					System.out.println(rs.getInt(1) + ", " + rs.getBoolean(2));
-				}
-				/* this call should cause resources on the server to be
-				 * freed */
-				pstmt.close();
-			}
-		} catch (SQLException e) {
-			System.out.println("FAILED :( "+ e.getMessage());
-			System.out.println("ABORTING TEST!!!");
-		}
-
-		stmt.close();
-		con.close();
-	}
-}