changeset 175:8700d9ef2ace

Added possibility via a connection property to let the JDBC driver return columnType value Types.VARCHAR instead of Types.CLOB in case the result column of a ResultSet or parameter in a PreparedStatement is of data type 'clob'. With this connection property set, you can reduce the overhead when working with clob column data from generic JDBC programs.
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Thu, 28 Sep 2017 16:17:51 +0200 (2017-09-28)
parents bac9345e7d99
children e701b51b3552
files ChangeLog src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java src/main/java/nl/cwi/monetdb/jdbc/MonetDriver.java.in src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java
diffstat 5 files changed, 83 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,28 @@
 # ChangeLog file for monetdb-java
 # This file is updated with Maddlog
 
+* Thu Sep 28 2017 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
+- Added possibility to let the JDBC driver return columnType value
+  Types.VARCHAR instead of Types.CLOB in case the result column of a
+  ResultSet or parameter in a PreparedStatement is of data type 'clob'.
+  To enable this behavior provide it as JDBC connection URL property:
+     treat_clob_as_varchar=true
+  For example: jdbc:monetdb://localhost:50000/demo?treat_clob_as_varchar=true
+  See also:
+  https://www.monetdb.org/Documentation/Manuals/SQLreference/Programming/JDBC
+  This custom clob mapping informs generic JDBC programs to fetch clob
+  column values via method ResultSet.getString() instead of getClob()
+  and Clob.getCharacterStream() and next fetching from the stream.
+  As MonetDB server MAPI communication protocol do not support
+  streaming of parts of a CLOB value, the current implementation is to
+  send over the whole CLOB value as a string. Therefore there is no
+  performance gain when fetching those Clob values via getClob() and
+  getCharacterStream(). In fact it creates a lot of overhead (additional
+  objects and method calls) and triples the amount of needed Java Heap memory
+  for each Clob value!
+  With this connection property set, you can reduce this overhead when
+  working with clob column data from generic JDBC programs.
+
 * Thu Sep  7 2017 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
 - Implemented PreparedStatement methods setNCharacterStream(int, Reader)
   and setNCharacterStream(int, Reader, long).
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java
+++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.java
@@ -141,8 +141,10 @@ public class MonetConnection
 	/** The language which is used */
 	final int lang;
 
-	/** Whether or not BLOB is mapped to BINARY within the driver */
-	private final boolean blobIsBinary;
+	/** Whether or not BLOB is mapped to Types.VARBINARY instead of Types.BLOB within this connection */
+	private boolean treatBlobAsVarBinary = false;
+	/** Whether or not CLOB is mapped to Types.VARCHAR instead of Types.CLOB within this connection */
+	private boolean treatClobAsVarChar = false;
 
 	/**
 	 * Constructor of a Connection for MonetDB. At this moment the
@@ -201,12 +203,18 @@ public class MonetConnection
 		if (hash != null)
 			conn_props.setProperty("hash", hash);
 
-		String blobIsBinary_prop = props.getProperty("treat_blob_as_binary");
-		if (blobIsBinary_prop != null) {
-			blobIsBinary = Boolean.parseBoolean(blobIsBinary_prop);
-			conn_props.setProperty("treat_blob_as_binary", Boolean.toString(blobIsBinary));
-		} else {
-			blobIsBinary = false;
+		String treatBlobAsVarBinary_prop = props.getProperty("treat_blob_as_binary");
+		if (treatBlobAsVarBinary_prop != null) {
+			treatBlobAsVarBinary = Boolean.parseBoolean(treatBlobAsVarBinary_prop);
+			conn_props.setProperty("treat_blob_as_binary", Boolean.toString(treatBlobAsVarBinary));
+			typeMap.put("blob", Byte[].class);
+		}
+
+		String treatClobAsVarChar_prop = props.getProperty("treat_clob_as_varchar");
+		if (treatClobAsVarChar_prop != null) {
+			treatClobAsVarChar = Boolean.parseBoolean(treatClobAsVarChar_prop);
+			conn_props.setProperty("treat_clob_as_varchar", Boolean.toString(treatClobAsVarChar));
+			typeMap.put("clob", String.class);
 		}
 
 		int sockTimeout = 0;
@@ -1252,8 +1260,11 @@ public class MonetConnection
 			}
 		} catch (SQLException se) {
 			String msg = se.getMessage();
-			// System.out.println("Con.isValid(): " + msg);
-			if (msg != null && msg.equals("Current transaction is aborted (please ROLLBACK)")) {
+			// 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.
+				// It changed to 'Current' after Jul2017 release. We need to support all server versions.
+				// SQLState = 25005
 				isValid = true;
 			}
 			/* ignore stmt errors/exceptions, we are only testing if the connection is still alive and usable */
@@ -1371,7 +1382,9 @@ public class MonetConnection
 		    name.equals("so_timeout") ||
 		    name.equals("debug") ||
 		    name.equals("hash") ||
-		    name.equals("treat_blob_as_binary")) {
+		    name.equals("treat_blob_as_binary") ||
+		    name.equals("treat_clob_as_varchar"))
+		{
 			conn_props.setProperty(name, value);
 		} else {
 			addWarning("setClientInfo: " + name + "is not a recognised property", "01M07");
@@ -1571,12 +1584,22 @@ public class MonetConnection
 	}
 
 	/**
-	 * Returns whether the BLOB type should be mapped to BINARY type.
+	 * Returns 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.
 	 */
-	boolean getBlobAsBinary() {
-		return blobIsBinary;
+	boolean mapBlobAsVarBinary() {
+		return treatBlobAsVarBinary;
 	}
 
+	/**
+	 * Returns whether the JDBC CLOB type should be mapped to VARCHAR type.
+	 * This allows generic JDBC programs to fetch Clob data via getString()
+	 * instead of getClob() and Clob.getCharacterStream() to reduce overhead.
+	 */
+	boolean mapClobAsVarChar() {
+		return treatClobAsVarChar;
+	}
 
 	/**
 	 * Sends the given string to MonetDB as special transaction command.
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetDriver.java.in
+++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetDriver.java.in
@@ -253,6 +253,11 @@ final public class MonetDriver implement
 		prop.description = "Whether BLOBs on the server should be treated as BINARY types, thus mapped to byte[]";
 		props.add(prop);
 
+		prop = new DriverPropertyInfo("treat_clob_as_varchar", "false");
+		prop.required = false;
+		prop.description = "Whether CLOBs on the server should be treated and handled as VARCHAR types in the JDBC driver"; // recommend for increased performance due to less overhead
+		props.add(prop);
+
 		prop = new DriverPropertyInfo("so_timeout", "0");
 		prop.required = false;
 		prop.description = "Defines the maximum time to wait in milliseconds on a blocking read socket call"; // this corresponds to the Connection.setNetworkTimeout() method introduced in JDBC 4.1
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java
+++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetPreparedStatement.java
@@ -156,6 +156,14 @@ public class MonetPreparedStatement
 			for (int i = 0; rs.next(); i++) {
 				monetdbType[i] = rs.getString(type_colnr);
 				javaType[i] = MonetDriver.getJavaType(monetdbType[i]);
+				if (javaType[i] == Types.CLOB) {
+					if (connection.mapClobAsVarChar())
+						javaType[i] = Types.VARCHAR;
+				} else
+				if (javaType[i] == Types.BLOB) {
+					if (connection.mapBlobAsVarBinary())
+						javaType[i] = Types.VARBINARY;
+				}
 				digits[i] = rs.getInt(digits_colnr);
 				scale[i] = rs.getInt(scale_colnr);
 				if (rscolcnt == 3)
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java
+++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java
@@ -196,14 +196,21 @@ public class MonetResultSet
 	 * thereby improving getXyz() method performance.
 	 */
 	private void populateJdbcSQLtypesArray() {
+		MonetConnection connection = null;
+		try {
+			connection = (MonetConnection)statement.getConnection();
+		} catch (SQLException se) { /* ignore it */ }
+
 		for (int i = 0; i < types.length; i++) {
 			int javaSQLtype = MonetDriver.getJavaType(types[i]);
 			JdbcSQLTypes[i] = javaSQLtype;
+			if (javaSQLtype == Types.CLOB) {
+				if (connection != null && connection.mapClobAsVarChar())
+					JdbcSQLTypes[i] = Types.VARCHAR;
+			} else
 			if (javaSQLtype == Types.BLOB) {
-				try {
-					if (((MonetConnection)statement.getConnection()).getBlobAsBinary())
-						JdbcSQLTypes[i] = Types.BINARY;
-				} catch (SQLException se) { /* ignore it */ }
+				if (connection != null && connection.mapBlobAsVarBinary())
+					JdbcSQLTypes[i] = Types.VARBINARY;
 			}
 		}
 	}