changeset 18:8e57d20b5e80

Corrected implementation of java.sql.Wrapper methods isWrapperFor() and unwrap(). They now properly return expected results instead of always return false or throw an SQLException. Added test program Test_Wrapper.java
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Thu, 13 Oct 2016 16:31:08 +0200 (2016-10-13)
parents 1bf638b6a7c2
children 7acc09f59b21
files ChangeLog src/main/java/nl/cwi/monetdb/jdbc/MonetWrapper.java tests/Test_Wrapper.java tests/build.xml
diffstat 4 files changed, 159 insertions(+), 41 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,11 @@
 # ChangeLog file for java
 # This file is updated with Maddlog
 
+* Thu Oct 13 2016 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
+- Corrected implementation of java.sql.Wrapper methods isWrapperFor()
+  and unwrap().  They now properly return expected results instead of
+  always return false or throw an SQLException.
+
 * Thu Oct  6 2016 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
 - Corrected return values of DatabaseMetaData methods nullsAreSortedHigh(),
   nullsAreSortedLow(), getMaxCursorNameLength(), getMaxProcedureNameLength(),
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetWrapper.java
+++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetWrapper.java
@@ -8,66 +8,71 @@
 
 package nl.cwi.monetdb.jdbc;
 
-import java.sql.*;
+import java.sql.SQLException;
 
 /**
- * A Wrapper class that implements nothing.
- * 
- * This Class is used to simply provide JDBC4 Wrapper functions, for as
- * long as we don't really understand that they are for and what they
- * are supposed to do.  Hence the implementations are very stupid and
- * non-useful, ignoring any argument and claiming stuff doesn't work, or
- * won't work out.
+ * A Wrapper class which provide the ability to retrieve the delegate instance
+ * when the instance in question is in fact a proxy class.
  *
- * @author Fabian Groffen
- * @version 1.0
+ * The wrapper pattern is employed by many JDBC driver implementations to provide
+ * extensions beyond the traditional JDBC API that are specific to a data source.
+ * Developers may wish to gain access to these resources that are wrapped (the delegates)
+ * as proxy class instances representing the the actual resources.
+ * This class contains a standard mechanism to access these wrapped resources
+ * represented by their proxy, to permit direct access to the resource delegates.
+ *
+ * @author Fabian Groffen, Martin van Dinther
+ * @version 1.1
  */
-public class MonetWrapper implements Wrapper {
+public class MonetWrapper implements java.sql.Wrapper {
 	/**
 	 * Returns true if this either implements the interface argument or
 	 * is directly or indirectly a wrapper for an object that does.
 	 * Returns false otherwise. If this implements the interface then
 	 * return true, else if this is a wrapper then return the result of
-	 * recursively calling isWrapperFor on the wrapped object. If this
-	 * does not implement the interface and is not a wrapper, return
+	 * recursively calling <code>isWrapperFor</code> on the wrapped object.
+	 * If this does not implement the interface and is not a wrapper, return
 	 * false. This method should be implemented as a low-cost operation
-	 * compared to unwrap so that callers can use this method to avoid
-	 * expensive unwrap calls that may fail. If this method returns true
-	 * then calling unwrap with the same argument should succeed.
+	 * compared to <code>unwrap</code> so that callers can use this method to avoid
+	 * expensive <code>unwrap</code> calls that may fail.
+	 * If this method returns true then calling <code>unwrap</code> with the same argument should succeed.
 	 *
 	 * @param iface a Class defining an interface.
-	 * @return true if this implements the interface or directly or
-	 *         indirectly wraps an object that does.
-	 * @throws SQLException if an error occurs while determining
-	 *         whether this is a wrapper for an object with the given
-	 *         interface.
+	 * @return true if this implements the interface or directly or indirectly wraps an object that does.
+	 * @throws SQLException if an error occurs while determining whether this is a wrapper
+	 * 	for an object with the given interface.
+	 * @since 1.6
 	 */
 	@Override
 	public boolean isWrapperFor(Class<?> iface) throws SQLException {
-		return false;
+		return iface != null && iface.isAssignableFrom(getClass());
 	}
 
 	/**
 	 * Returns an object that implements the given interface to allow
-	 * access to non-standard methods, or standard methods not exposed
-	 * by the proxy. If the receiver implements the interface then the
-	 * result is the receiver or a proxy for the receiver. If the
-	 * receiver is a wrapper and the wrapped object implements the
-	 * interface then the result is the wrapped object or a proxy for
-	 * the wrapped object. Otherwise return the the result of calling
-	 * unwrap recursively on the wrapped object or a proxy for that
-	 * result. If the receiver is not a wrapper and does not implement
-	 * the interface, then an SQLException is thrown.
+	 * access to non-standard methods, or standard methods not exposed by the proxy.
+	 * The result may be either the object found to implement the interface
+	 * or a proxy for that object.
+	 * If the receiver implements the interface then the result is the receiver
+	 * or a proxy for the receiver.
+	 * If the receiver is a wrapper and the wrapped object implements the interface
+	 * then the result is the wrapped object or a proxy for the wrapped object.
+	 * Otherwise return the result of calling <code>unwrap</code> recursively on
+	 * the wrapped object or a proxy for that result.
+	 * If the receiver is not a wrapper and does not implement the interface,
+	 * then an <code>SQLException</code> is thrown.
 	 *
-	 * @param iface A Class defining an interface that the result must
-	 *        implement.
-	 * @return an object that implements the interface. May be a proxy
-	 *         for the actual implementing object.
-	 * @throws SQLException If no object found that implements the
-	 *         interface
+	 * @param iface A Class defining an interface that the result must implement.
+	 * @return an object that implements the interface. May be a proxy for the actual implementing object.
+	 * @throws SQLException If no object found that implements the interface
+	 * @since 1.6
 	 */
 	@Override
+	@SuppressWarnings("unchecked")
 	public <T> T unwrap(Class<T> iface) throws SQLException {
-		throw new SQLException("No object found (not implemented)", "0A000");
+		if (isWrapperFor(iface)) {
+			return (T) this;
+		}
+		throw new SQLException("Cannot unwrap to interface: " + (iface != null ? iface.getName() : ""), "0A000");
 	}
 }
new file mode 100644
--- /dev/null
+++ b/tests/Test_Wrapper.java
@@ -0,0 +1,102 @@
+/*
+ * 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 - 2016 MonetDB B.V.
+ */
+
+import java.sql.*;
+
+public class Test_Wrapper {
+	public static void main(String[] args) throws Exception {
+		// Class.forName("nl.cwi.monetdb.jdbc.MonetDriver");
+		final Connection con = DriverManager.getConnection(args[0]);
+		System.out.println("Connected. Auto commit is: " + con.getAutoCommit());
+
+		try {
+			final String jdbc_pkg = "java.sql.";
+			final String monetdb_jdbc_pkg = "nl.cwi.monetdb.jdbc.";
+
+			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)
+				System.out.println("FAILED :( " + e.getMessage());
+		}
+		con.close();
+	}
+
+	private static void checkIsWrapperFor(String objnm, Wrapper obj, String pkgnm, String classnm) {
+		try {
+			Class<?> clazz = Class.forName(pkgnm + classnm);
+			boolean isWrapper = obj.isWrapperFor(clazz);
+			System.out.print(objnm + ". isWrapperFor(" + classnm + ") returns: " + isWrapper);
+			if (isWrapper) {
+				Object wobj = obj.unwrap(clazz);
+				System.out.print("\tCalled unwrap(). Returned object is " + (wobj != null ? "not null, so oke" : "null !!"));
+			}
+			System.out.println();
+		} catch (ClassNotFoundException cnfe) {
+			System.out.println(cnfe.toString());
+		} catch (SQLException se) {
+			System.out.println(se.getMessage());
+		}
+	}
+}
--- a/tests/build.xml
+++ b/tests/build.xml
@@ -125,6 +125,7 @@ Copyright 1997 - July 2008 CWI, August 2
     <antcall target="Test_Smoreresults" />
     <antcall target="Test_Int128" />
     <antcall target="Test_FetchSize" />
+    <antcall target="Test_Wrapper" />
     <antcall target="BugConcurrent_clients_SF_1504657" />
     <antcall target="BugConcurrent_sequences" />
     <antcall target="BugDatabaseMetaData_Bug_3356" />
@@ -142,7 +143,6 @@ Copyright 1997 - July 2008 CWI, August 2
       <arg value="${jdbc_url}" />
     </java>
   </target>
-  
 
   <!-- convenience targets for the outside caller to specify which
   test(s) should be run -->
@@ -319,13 +319,19 @@ Copyright 1997 - July 2008 CWI, August 2
       <param name="test.class" value="Test_Int128" />
     </antcall>
   </target>
-  
+
   <target name="Test_FetchSize">
     <antcall target="test_class">
       <param name="test.class" value="Test_FetchSize" />
     </antcall>
   </target>
-  
+
+  <target name="Test_Wrapper">
+    <antcall target="test_class">
+      <param name="test.class" value="Test_Wrapper" />
+    </antcall>
+  </target>
+
   <target name="BugConcurrent_clients_SF_1504657">
     <antcall target="test_class">
       <param name="test.class" value="BugConcurrent_clients_SF_1504657" />