From 5e8e0328a93cd02d3d47cd41e79c0f5724739f52 Mon Sep 17 00:00:00 2001 From: bernd Date: Wed, 14 Nov 2012 21:24:31 +0100 Subject: [PATCH 12/25] Make EoDSQL work with JDBC drivers which do not support scrollable cursors (e.g. SQLite JDBC driver) for disconnected DataSets, Collections and arrays. If a connected DataSet is requested and the JDBC driver doesn't support scrollable cursors, provide a good error message. --- eodsql/src/net/lemnik/eodsql/QuickQueryUtil.java | 4 +- .../eodsql/impl/AbstractMethodImplementation.java | 66 ++++++++++++++++++--- .../src/net/lemnik/eodsql/impl/BaseQueryImpl.java | 7 ++- .../lemnik/eodsql/impl/TransactionQueryImpl.java | 5 +- eodsql/src/net/lemnik/eodsql/spi/Context.java | 16 +++++- .../eodsql/spi/util/CollectionWrapperFactory.java | 67 ++++++++++++++++++---- .../eodsql/spi/util/DisconnectedDataSet.java | 30 ++++++---- 7 files changed, 159 insertions(+), 36 deletions(-) diff --git a/eodsql/src/net/lemnik/eodsql/QuickQueryUtil.java b/eodsql/src/net/lemnik/eodsql/QuickQueryUtil.java index e9c347f..0bdc77d 100644 --- a/eodsql/src/net/lemnik/eodsql/QuickQueryUtil.java +++ b/eodsql/src/net/lemnik/eodsql/QuickQueryUtil.java @@ -192,6 +192,7 @@ class QuickQueryUtil { final Context( DEFAULT_SELECT_PARAMETERS, + DataSet.class, parameters); try { @@ -231,7 +232,8 @@ class QuickQueryUtil { throws SQLException, ParseException { final Context context = new Context( - DEFAULT_UPDATE_PARAMETERS, + DEFAULT_UPDATE_PARAMETERS, + Integer.class, parameters); try { diff --git a/eodsql/src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java b/eodsql/src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java index 89b1388..7b884bb 100644 --- a/eodsql/src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java +++ b/eodsql/src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java @@ -3,19 +3,23 @@ package net.lemnik.eodsql.impl; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.sql.Connection; -import java.sql.SQLException; +import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; - +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; import java.util.Map; +import net.lemnik.eodsql.DataIterator; +import net.lemnik.eodsql.DataSet; +import net.lemnik.eodsql.InvalidQueryException; import net.lemnik.eodsql.QueryTool; +import net.lemnik.eodsql.Select; import net.lemnik.eodsql.TypeMapper; -import net.lemnik.eodsql.InvalidQueryException; - import net.lemnik.eodsql.spi.Context; -import net.lemnik.eodsql.spi.StatementResource; import net.lemnik.eodsql.spi.MethodImplementation; - +import net.lemnik.eodsql.spi.StatementResource; import net.lemnik.eodsql.spi.util.Query; import net.lemnik.eodsql.spi.util.ResultSetWrapper; @@ -26,6 +30,12 @@ import net.lemnik.eodsql.spi.util.ResultSetWrapper; abstract class AbstractMethodImplementation implements MethodImplementation { + private final static List ALL_RESULT_SET_TYPES = Arrays.asList( + ResultSet.TYPE_SCROLL_SENSITIVE, + ResultSet.TYPE_SCROLL_INSENSITIVE, + ResultSet.TYPE_FORWARD_ONLY + ); + protected ResultSetWrapper wrapper = null; protected Query query = null; @@ -37,15 +47,57 @@ abstract class AbstractMethodImplementation throws SQLException { final Connection connection = context.getResource(Connection.class).get(); + int preferredResultSetType = wrapper.getPreferredResultSetType(); + int resultSetType = getResultSetType(connection.getMetaData(), preferredResultSetType); + if (supportsScrolling(preferredResultSetType) && false == supportsScrolling(resultSetType)) { + if (isDisconnected(context)) { + resultSetType = ResultSet.TYPE_FORWARD_ONLY; + } else { + final String dbName = connection.getMetaData().getDatabaseProductName(); + throw new SQLException("This database (" + dbName + + ") does not support scrollable cursors, thus connected DataSets are not available."); + } + } final PreparedStatement statement = connection.prepareStatement( query.toString(), - wrapper.getPreferredResultSetType(), + resultSetType, wrapper.getPreferredResultSetConcurrency()); context.setResource(new StatementResource(statement)); return statement; } + + private int getResultSetType(DatabaseMetaData dbMetaData, int preferredResultSetType) throws SQLException { + if (dbMetaData.supportsResultSetType(preferredResultSetType)) { + return preferredResultSetType; + } + for (int type : ALL_RESULT_SET_TYPES) { + if (dbMetaData.supportsResultSetType(type)) { + return type; + } + } + // We assume that the dbMetaData.supportsResultSetType() call lied to us and that the most + // basic result set is supported. + return ResultSet.TYPE_FORWARD_ONLY; + } + + private boolean supportsScrolling(int resultSetType) { + return resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE + || resultSetType == ResultSet.TYPE_SCROLL_INSENSITIVE; + } + + private boolean isDisconnected(final Context context) { + if (context.getAnnotation() instanceof Select + && ((Select) context.getAnnotation()).disconnected()) { + return true; + } + if (DataSet.class.isAssignableFrom(context.getReturnType()) + || DataIterator.class.isAssignableFrom(context.getReturnType())) { + return false; + } + return true; + } protected void fillPreparedStatementParameters( final Context context, diff --git a/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java b/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java index a2e6ee7..1c71ff0 100644 --- a/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java +++ b/eodsql/src/net/lemnik/eodsql/impl/BaseQueryImpl.java @@ -167,8 +167,9 @@ class BaseQueryImpl implements InvocationHandler { } } - protected Context createContext(Annotation annotation, final Object[] args) { - return new Context(annotation, args); + protected Context createContext(final Annotation annotation, + final Class returnType, final Object[] args) { + return new Context(annotation, returnType, args); } static interface Callable { @@ -210,7 +211,7 @@ class BaseQueryImpl implements InvocationHandler { final Object[] args) throws Throwable { - final Context context = createContext(annotation, args); + final Context context = createContext(annotation, method.getReturnType(), args); final Resource connection = new ConnectionSourceConnectionResource(connectionSource); diff --git a/eodsql/src/net/lemnik/eodsql/impl/TransactionQueryImpl.java b/eodsql/src/net/lemnik/eodsql/impl/TransactionQueryImpl.java index 212808d..57c1ee8 100644 --- a/eodsql/src/net/lemnik/eodsql/impl/TransactionQueryImpl.java +++ b/eodsql/src/net/lemnik/eodsql/impl/TransactionQueryImpl.java @@ -44,9 +44,10 @@ class TransactionQueryImpl extends BaseQueryImpl { } @Override - protected Context createContext(Annotation annotation, final Object[] args) + protected Context createContext(final Annotation annotation, + final Class returnType, final Object[] args) { - final Context context = new Context(annotation, args); + final Context context = new Context(annotation, returnType, args); if (noAutoClose) { context.setDontCloseConnection(true); diff --git a/eodsql/src/net/lemnik/eodsql/spi/Context.java b/eodsql/src/net/lemnik/eodsql/spi/Context.java index b9acf36..6a89dd8 100644 --- a/eodsql/src/net/lemnik/eodsql/spi/Context.java +++ b/eodsql/src/net/lemnik/eodsql/spi/Context.java @@ -45,6 +45,8 @@ public class Context { private final A annotation; private final Object[] parameters; + + private final Class returnType; private Object returnValue = null; @@ -70,10 +72,12 @@ public class Context { *

* * @param annotation the annotation containing the methods invocation details + * @param returnType the class of the return value * @param parameters the parameters passed to the method being invoked */ - public Context(final A annotation, final Object[] parameters) { + public Context(final A annotation, final Class returnType, final Object[] parameters) { this.annotation = annotation; + this.returnType = returnType; this.parameters = parameters != null ? parameters : EMPTY_PARAMETERS; @@ -113,6 +117,7 @@ public class Context
{ } this.annotation = parentContext.getAnnotation(); + this.returnType = parentContext.returnType; this.resources.putAll(parentContext.resources); this.parameters = newParameters; this.autoclose = parentContext.autoclose; @@ -138,6 +143,15 @@ public class Context { } /** + * Returns the class of the return value. + * + * @return the class of the return value + */ + public Class getReturnType() { + return returnType; + } + + /** * Returns the parameters that were specified for this method invocation. This * method will always return a non-null array, but the array may be empty. * diff --git a/eodsql/src/net/lemnik/eodsql/spi/util/CollectionWrapperFactory.java b/eodsql/src/net/lemnik/eodsql/spi/util/CollectionWrapperFactory.java index 219a6dd..abac6ec 100644 --- a/eodsql/src/net/lemnik/eodsql/spi/util/CollectionWrapperFactory.java +++ b/eodsql/src/net/lemnik/eodsql/spi/util/CollectionWrapperFactory.java @@ -218,7 +218,7 @@ class CollectionWrapperFactory implements ResultSetWrapper.Factory { super(binding); } - protected abstract T newCollectionInstance(Collection objects); + protected abstract T newCollectionInstance(ResultSetCollection objects) throws SQLException; @Override protected T wrap(final ResultSet results) throws SQLException { @@ -236,8 +236,16 @@ class CollectionWrapperFactory implements ResultSetWrapper.Factory { } @Override - protected List newCollectionInstance(final Collection objects) { - return new ArrayList(objects); + protected List newCollectionInstance(final ResultSetCollection objects) throws SQLException { + if (objects.supportsScrolling()) { + return new ArrayList(objects); + } else { + final ArrayList collection = new ArrayList(); + for (Object o : objects) { + collection.add(o); + } + return collection; + } } } @@ -249,8 +257,16 @@ class CollectionWrapperFactory implements ResultSetWrapper.Factory { } @Override - protected Set newCollectionInstance(final Collection objects) { - return new HashSet(objects); + protected Set newCollectionInstance(final ResultSetCollection objects) throws SQLException { + if (objects.supportsScrolling()) { + return new HashSet(objects); + } else { + final HashSet collection = new HashSet(); + for (Object o : objects) { + collection.add(o); + } + return collection; + } } } @@ -264,8 +280,16 @@ class CollectionWrapperFactory implements ResultSetWrapper.Factory { } @Override - protected SortedSet newCollectionInstance(final Collection objects) { - return new TreeSet(objects); + protected SortedSet newCollectionInstance(final ResultSetCollection objects) throws SQLException { + if (objects.supportsScrolling()) { + return new TreeSet(objects); + } else { + final TreeSet collection = new TreeSet(); + for (Object o : objects) { + collection.add(o); + } + return collection; + } } } @@ -308,10 +332,19 @@ class CollectionWrapperFactory implements ResultSetWrapper.Factory { @Override protected Collection newCollectionInstance( - final Collection objects) { + final ResultSetCollection objects) throws SQLException { try { - return constructor.newInstance(objects); + if (objects.supportsScrolling()) { + return constructor.newInstance(objects); + } else { + @SuppressWarnings("unchecked") + final Collection collection = collectionType.newInstance(); + for (Object o : objects) { + collection.add(o); + } + return collection; + } } catch(final InstantiationException ie) { throw new EoDException(ie); } catch(final IllegalAccessException iae) { @@ -337,11 +370,17 @@ class CollectionWrapperFactory implements ResultSetWrapper.Factory { @Override @SuppressWarnings("unchecked") protected Collection newCollectionInstance( - final Collection objects) { + final ResultSetCollection objects) throws SQLException { try { final Collection collection = collectionType.newInstance(); - collection.addAll(objects); + if (objects.supportsScrolling()) { + collection.addAll(objects); + } else { + for (Object o : objects) { + collection.add(o); + } + } return collection; } catch(final InstantiationException ie) { throw new EoDException(ie); @@ -406,7 +445,7 @@ class CollectionWrapperFactory implements ResultSetWrapper.Factory { @Override public Iterator iterator() { try { - if(!results.isBeforeFirst()) { + if(!results.isBeforeFirst() && supportsScrolling()) { results.beforeFirst(); } } catch(final SQLException sqle) { @@ -472,5 +511,9 @@ class CollectionWrapperFactory implements ResultSetWrapper.Factory { : 0; } + boolean supportsScrolling() throws SQLException { + return results.getType() != ResultSet.TYPE_FORWARD_ONLY; + } + } } diff --git a/eodsql/src/net/lemnik/eodsql/spi/util/DisconnectedDataSet.java b/eodsql/src/net/lemnik/eodsql/spi/util/DisconnectedDataSet.java index f1424a8..4281013 100644 --- a/eodsql/src/net/lemnik/eodsql/spi/util/DisconnectedDataSet.java +++ b/eodsql/src/net/lemnik/eodsql/spi/util/DisconnectedDataSet.java @@ -3,6 +3,7 @@ package net.lemnik.eodsql.spi.util; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Collection; import java.util.AbstractList; @@ -29,16 +30,25 @@ class DisconnectedDataSet extends AbstractList implements DataSet { context.getResource(ResultSet.class); final ResultSet results = resultsResource.get(); - results.last(); - - final int size = results.getRow(); - content = new Object[size]; - - results.beforeFirst(); - - int i = 0; - while(results.next()) { - content[i++] = mapper.unmarshall(results); + if (results.getType() == ResultSet.TYPE_FORWARD_ONLY) { + // Cannot determine size + final ArrayList contentList = new ArrayList(); + while(results.next()) { + contentList.add(mapper.unmarshall(results)); + } + content = contentList.toArray(); + } else { + results.last(); + + final int size = results.getRow(); + content = new Object[size]; + + results.beforeFirst(); + + int i = 0; + while(results.next()) { + content[i++] = mapper.unmarshall(results); + } } } -- 1.8.0