From cc1bba56816175ff9ab8e7c666f70f03f45701f8 Mon Sep 17 00:00:00 2001 From: bernd Date: Wed, 14 Nov 2012 21:24:31 +0100 Subject: [PATCH 22/25] Add support for setting a query timeout. --- eodsql/src/net/lemnik/eodsql/Call.java | 24 ++ eodsql/src/net/lemnik/eodsql/QuickQueryUtil.java | 16 ++ eodsql/src/net/lemnik/eodsql/Select.java | 250 +++++++++++---------- eodsql/src/net/lemnik/eodsql/Update.java | 24 ++ .../eodsql/impl/AbstractMethodImplementation.java | 80 +++++-- .../eodsql/impl/SelectDynamicParameterFactory.java | 8 + .../eodsql/impl/UpdateDynamicParameterFactory.java | 8 + .../lemnik/eodsql/PrimitiveQueryWithTimeout.java | 57 +++++ eodsql/test/net/lemnik/eodsql/SetTimeoutTest.java | 161 +++++++++++++ 9 files changed, 493 insertions(+), 135 deletions(-) create mode 100644 eodsql/test/net/lemnik/eodsql/PrimitiveQueryWithTimeout.java create mode 100644 eodsql/test/net/lemnik/eodsql/SetTimeoutTest.java diff --git a/eodsql/src/net/lemnik/eodsql/Call.java b/eodsql/src/net/lemnik/eodsql/Call.java index a3d6f79..b55dd59 100644 --- a/eodsql/src/net/lemnik/eodsql/Call.java +++ b/eodsql/src/net/lemnik/eodsql/Call.java @@ -135,4 +135,28 @@ public @interface Call { * @since 2.2 */ Class[] parameterBindings() default {}; + + /** + *

+ * The query timeout (in seconds). The call is cancelled if it hasn't returned a result after + * this time. + *

+ * 0 means: no timeout. + * + * @since 2.3 + */ + int timeout() default 0; + + /** + *

+ * The index of the method parameter (starting with 1) which will be used as a query timeout + * (in seconds). The parameter type has to be an int or Integer. If + * both {@link #timeout()} and {@link #timeoutParameterIndex()} are specified, this one takes + * precedence. + *

+ * 0 means: no timeout. + * + * @since 2.3 + */ + int timeoutParameterIndex() default 0; } diff --git a/eodsql/src/net/lemnik/eodsql/QuickQueryUtil.java b/eodsql/src/net/lemnik/eodsql/QuickQueryUtil.java index 763a56d..4376f27 100644 --- a/eodsql/src/net/lemnik/eodsql/QuickQueryUtil.java +++ b/eodsql/src/net/lemnik/eodsql/QuickQueryUtil.java @@ -92,6 +92,14 @@ class QuickQueryUtil { return new Class[0]; } + public int timeout() { + return 0; + } + + public int timeoutParameterIndex() { + return 0; + } + }; private static final Update DEFAULT_UPDATE_PARAMETERS = new Update() { @@ -120,6 +128,14 @@ class QuickQueryUtil { public Class annotationType() { return Update.class; } + + public int timeout() { + return 0; + } + + public int timeoutParameterIndex() { + return 0; + } }; private QuickQueryUtil() { diff --git a/eodsql/src/net/lemnik/eodsql/Select.java b/eodsql/src/net/lemnik/eodsql/Select.java index d84fb1b..976726a 100644 --- a/eodsql/src/net/lemnik/eodsql/Select.java +++ b/eodsql/src/net/lemnik/eodsql/Select.java @@ -18,7 +18,8 @@ import net.lemnik.eodsql.spi.util.DataObjectBinding; * {@link BaseQuery Query} class that will return some form of data from * database (typically a {@literal "SELECT"} query. The {@code Select} * annotation contains the SQL query that will be executed on the database - *

+ *

+ *

* Here is an example of how a @Select is used: * *

@@ -37,41 +38,38 @@ import net.lemnik.eodsql.spi.util.DataObjectBinding;
  * following datatypes:
  * 
  * 
- * 

- * Columns that cannot be mapped to fields are ignored, as are fields - * that cannot be mapped to columns. @Select SQL uses the - * exact same query parser as the {@link Update} annotation, and so can - * also accept complex types as it's parameters, and insert their - * fields into the SQL query. *

+ *

+ * Columns that cannot be mapped to fields are ignored, as are fields that + * cannot be mapped to columns. @Select SQL uses the exact same + * query parser as the {@link Update} annotation, and so can also accept complex + * types as it's parameters, and insert their fields into the SQL query. + *

+ * * @author Jason Morris * @see Update @Update * @see Call @Call @@ -81,10 +79,10 @@ import net.lemnik.eodsql.spi.util.DataObjectBinding; public @interface Select { /** - * The SQL query to execute on the database. The result set of - * the query will be mapped to the datatype specified in the - * method declaration. This attribute is used for convenience - * when none of the other attributes need to be explicidly set. + * The SQL query to execute on the database. The result set of the query + * will be mapped to the datatype specified in the method declaration. This + * attribute is used for convenience when none of the other attributes need + * to be explicidly set. * * @see #sql * @return the EoD SQL query to execute against the database @@ -93,39 +91,37 @@ public @interface Select { /** *

- * The SQL query to execute on the database. The result set of the - * query will be mapped to the datatype specified in the method - * declaration. This attribute works in the exact same way as - * {@link #value}, but is used when you wish to specify values - * other than the SQL query. - *

- * The SQL is partly parsed by the implementation, allowing for - * parameters of the annotated method to be used in the SQL - * statement. Selecting a specific {@code User} object from the - * database may look as follows: + * The SQL query to execute on the database. The result set of the query + * will be mapped to the datatype specified in the method declaration. This + * attribute works in the exact same way as {@link #value}, but is used when + * you wish to specify values other than the SQL query. + *

+ *

+ * The SQL is partly parsed by the implementation, allowing for parameters + * of the annotated method to be used in the SQL statement. Selecting a + * specific {@code User} object from the database may look as follows: *

* * @Select("SELECT * FROM user WHERE id = ?1")
* User selectUserById(int id) throws SQLException; *
*

- * More complex introspection of the method parameters can be done - * using the bracketed syntax as follows: + * More complex introspection of the method parameters can be done using the + * bracketed syntax as follows: *

* * @Select("SELECT * FROM user WHERE email = ?{1.email}")
* DataIterator<User> selectUsersByUserEmail(User newUser) throws SQLException; *
- * + * * @return the SQL query to execute against the database */ String sql() default ""; /** - * Specify whether or not the {@link DataSet} resulting from - * a call to the annotated method will be connected to the - * database or not. This will generally only have an effect on - * {@code DataSet}s. + * Specify whether or not the {@link DataSet} resulting from a call to the + * annotated method will be connected to the database or not. This will + * generally only have an effect on {@code DataSet}s. * * @see DataSet * @see DataSet#disconnect @@ -134,15 +130,14 @@ public @interface Select { boolean disconnected() default false; /** - * If rubberstamp is flagged as true, - * the annotated method must return a {@link DataIterator}, - * otherwise it will be rejected by the {@link QueryTool}. - * Rubber-stamping will cause the returned {@code DataIterator} - * to use a single instance of the data-object for all of the - * rows returned, instead of initializing a new one for each row. - * This can be used to save CPU time and memory, and is - * especially useful in display code. - * + * If rubberstamp is flagged as true, the + * annotated method must return a {@link DataIterator}, otherwise it will be + * rejected by the {@link QueryTool}. Rubber-stamping will cause the + * returned {@code DataIterator} to use a single instance of the data-object + * for all of the rows returned, instead of initializing a new one for each + * row. This can be used to save CPU time and memory, and is especially + * useful in display code. + * * @see DataIterator * @return {@literal false} by default */ @@ -155,55 +150,56 @@ public @interface Select { * {@code DataSet} will implement the following additional methods: *

* *

* It is considered a validation error for a method to have readOnly set * {@literal false} if it doesn't return a {@code DataSet}. *

- * + * * @since 1.1 * @return {@literal true} by default */ boolean readOnly() default true; /** - *

- * The cache class to be used by {@code DataSet}s returned by the - * annotated method. This only has an effect of methods that return a - * {@link DataSet} type. - *

+ *

+ * The cache class to be used by {@code DataSet}s returned by the annotated + * method. This only has an effect of methods that return a {@link DataSet} + * type. + *

+ *

* The default {@code DataSetCache} implementation is - * {@link ArrayDataSetCache}, which uses an array to cache - * the row values. For larger {@code ResultSet}s, you - * may want to look into a {@link NullDataSetCache} or using - * a {@link DataIterator} instead of the {@code DataSet} class. - *

- * + * {@link ArrayDataSetCache}, which uses an array to cache the row values. + * For larger {@code ResultSet}s, you may want to look into a + * {@link NullDataSetCache} or using a {@link DataIterator} instead of the + * {@code DataSet} class. + *

+ * * @see DataSet * @see ArrayDataSetCache * @return the {@code DataSetCache} type that the returned {@code DataSet} - * should proxy all of the returned objects through + * should proxy all of the returned objects through */ Class cache() default ArrayDataSetCache.class; /** *

- * This is a hint to the database driver to suggest the number of rows - * to fetch in a single page of the {@link ResultSet}. This value - * directly corresponds to the {@link Statement#setFetchSize(int)} - * method. - *

+ * This is a hint to the database driver to suggest the number of rows to + * fetch in a single page of the {@link ResultSet}. This value directly + * corresponds to the {@link Statement#setFetchSize(int)} method. + *

+ *

* If this value is {@literal 0} (which it is by default) then the - * fetch-size of the underlying {@code Statement} will be left alone. - * This value (like the {@code fetch-size} of a {@code Statement}) is - * to be considered a hint to EoD SQL. The underlying - * {@link MethodImplementation} may choose to ignore this value. + * fetch-size of the underlying {@code Statement} will be left alone. This + * value (like the {@code fetch-size} of a {@code Statement}) is to be + * considered a hint to EoD SQL. The underlying {@link MethodImplementation} + * may choose to ignore this value. *

- * + * * @since 2.1 * @return the suggested number of rows to be fetched at a time */ @@ -211,47 +207,75 @@ public @interface Select { /** *

- * This attribute optionally allows you to populate one of the parameters - * of the annotated method instead of returning a new object. The value - * of this attribute must point to the index of the parameter to be - * populated. The first parameter is {@literal 1}, the second is - * {@literal 2} and so on. The parameter must be a valid data-object, - * and thus may not be a {@code Collection}; array; primitive; etc. - *

+ * This attribute optionally allows you to populate one of the parameters of + * the annotated method instead of returning a new object. The value of this + * attribute must point to the index of the parameter to be populated. The + * first parameter is {@literal 1}, the second is {@literal 2} and so on. + * The parameter must be a valid data-object, and thus may not be a + * {@code Collection}; array; primitive; etc. + *

+ *

* This attribute can be used to either refresh data that has previously * been fetched from the database and/or to fetch new data into an object. - * This allows for a lazy-loading or fetch-group style of data loading - * from the database. - *

- * Finally: a method selecting into an existing object may only - * return {@code void} or the same type as the specified - * parameter (in which case the parameter itself will be returned). + * This allows for a lazy-loading or fetch-group style of data loading from + * the database. *

- * - * @return {@literal 0} by default to indicate that the method returns - * new objects instead of creating new ones + *

+ * Finally: a method selecting into an existing object may only return + * {@code void} or the same type as the specified parameter (in which + * case the parameter itself will be returned). + *

+ * + * @return {@literal 0} by default to indicate that the method returns new + * objects instead of creating new ones * @since 2.1 */ int into() default 0; /** *

- * The custom type bindings to be used on the parameters of this query. The binding is matched - * by position. The default type binding will be used for each parameter that has no - * parameterBindings entry or an entry that is of type {@link TypeMapper} itself. + * The custom type bindings to be used on the parameters of this query. The + * binding is matched by position. The default type binding will be used for + * each parameter that has no parameterBindings entry or an + * entry that is of type {@link TypeMapper} itself. *

* * @since 2.2 */ Class[] parameterBindings() default {}; - + /** *

- * A custom data object binding to be used on the result set of this query. The default value - * {@link NoDataObjectBinding} indicates that no custom binding is to be used. + * A custom data object binding to be used on the result set of this query. + * The default value {@link NoDataObjectBinding} indicates that no custom + * binding is to be used. *

* * @since 2.2 */ Class resultSetBinding() default NoDataObjectBinding.class; + + /** + *

+ * The query timeout (in seconds). The query is cancelled if it hasn't returned a result after + * this time. + *

+ * 0 means: no timeout. + * + * @since 2.3 + */ + int timeout() default 0; + + /** + *

+ * The index of the method parameter (starting with 1) which will be used as a query timeout + * (in seconds). The parameter type has to be an int or Integer. If + * both {@link #timeout()} and {@link #timeoutParameterIndex()} are specified, this one takes + * precedence. + *

+ * 0 means: no timeout. + * + * @since 2.3 + */ + int timeoutParameterIndex() default 0; } diff --git a/eodsql/src/net/lemnik/eodsql/Update.java b/eodsql/src/net/lemnik/eodsql/Update.java index b20090b..0c07f8c 100644 --- a/eodsql/src/net/lemnik/eodsql/Update.java +++ b/eodsql/src/net/lemnik/eodsql/Update.java @@ -121,4 +121,28 @@ public @interface Update { * @since 2.2 */ Class[] parameterBindings() default {}; + + /** + *

+ * The query timeout (in seconds). The update is cancelled if it hasn't returned a result after + * this time. + *

+ * 0 means: no timeout. + * + * @since 2.3 + */ + int timeout() default 0; + + /** + *

+ * The index of the method parameter (starting with 1) which will be used as a query timeout + * (in seconds). The parameter type has to be an int or Integer. + * If both {@link #timeout()} and {@link #timeoutParameterIndex()} are specified, this one takes + * precedence. + *

+ * 0 means: no timeout. + * + * @since 2.3 + */ + int timeoutParameterIndex() default 0; } diff --git a/eodsql/src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java b/eodsql/src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java index 7b884bb..6db9b1a 100644 --- a/eodsql/src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java +++ b/eodsql/src/net/lemnik/eodsql/impl/AbstractMethodImplementation.java @@ -11,12 +11,14 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import net.lemnik.eodsql.Call; 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.Update; import net.lemnik.eodsql.spi.Context; import net.lemnik.eodsql.spi.MethodImplementation; import net.lemnik.eodsql.spi.StatementResource; @@ -45,7 +47,7 @@ abstract class AbstractMethodImplementation protected PreparedStatement createPreparedStatement( final Context context) throws SQLException { - + final Connection connection = context.getResource(Connection.class).get(); int preferredResultSetType = wrapper.getPreferredResultSetType(); int resultSetType = getResultSetType(connection.getMetaData(), preferredResultSetType); @@ -55,19 +57,27 @@ abstract class AbstractMethodImplementation } else { final String dbName = connection.getMetaData().getDatabaseProductName(); throw new SQLException("This database (" + dbName - + ") does not support scrollable cursors, thus connected DataSets are not available."); + + ") does not support scrollable cursors, thus connected DataSets are not available."); } } final PreparedStatement statement = connection.prepareStatement( query.toString(), resultSetType, wrapper.getPreferredResultSetConcurrency()); + final int queryTimeout = getQueryTimeout(context); + if (queryTimeout > 0) { + try { + statement.setQueryTimeout(queryTimeout); + } catch (Exception ex) { + // Some JDBC drivers don't support setting a query timeout - keep quiet about it. + } + } context.setResource(new StatementResource(statement)); return statement; } - + private int getResultSetType(DatabaseMetaData dbMetaData, int preferredResultSetType) throws SQLException { if (dbMetaData.supportsResultSetType(preferredResultSetType)) { return preferredResultSetType; @@ -83,32 +93,58 @@ abstract class AbstractMethodImplementation } private boolean supportsScrolling(int resultSetType) { - return resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE + return resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE || resultSetType == ResultSet.TYPE_SCROLL_INSENSITIVE; } - + private boolean isDisconnected(final Context context) { - if (context.getAnnotation() instanceof Select + if (context.getAnnotation() instanceof Select && ((Select) context.getAnnotation()).disconnected()) { return true; } - if (DataSet.class.isAssignableFrom(context.getReturnType()) + if (DataSet.class.isAssignableFrom(context.getReturnType()) || DataIterator.class.isAssignableFrom(context.getReturnType())) { return false; } return true; } - protected void fillPreparedStatementParameters( - final Context context, - final PreparedStatement statement) - throws SQLException { + private int getQueryTimeout(final Context context) { + if (context.getAnnotation() instanceof Select) { + final Select select = (Select) context.getAnnotation(); + if (select.timeoutParameterIndex() > 0 + && context.getParameters()[select.timeoutParameterIndex() - 1] != null) { + return (Integer) context.getParameters()[select.timeoutParameterIndex() - 1]; + } + return select.timeout(); + } + if (context.getAnnotation() instanceof Update) { + final Update update = (Update) context.getAnnotation(); + if (update.timeoutParameterIndex() > 0 + && context.getParameters()[update.timeoutParameterIndex() - 1] != null) { + return (Integer) context.getParameters()[update.timeoutParameterIndex() - 1]; + } + return update.timeout(); + } + if (context.getAnnotation() instanceof Call) { + final Call call = (Call) context.getAnnotation(); + if (call.timeoutParameterIndex() > 0 + && context.getParameters()[call.timeoutParameterIndex() - 1] != null) { + return (Integer) context.getParameters()[call.timeoutParameterIndex() - 1]; + } + return call.timeout(); + } + return 0; + } + + protected void fillPreparedStatementParameters(final Context context, + final PreparedStatement statement) throws SQLException { final TypeMapper[] mappers = getParameterMappers(); - for(int i = 0; i < mappers.length; i++) { + for (int i = 0; i < mappers.length; i++) { final Object parameter = query.getParameter(context, i); - + @SuppressWarnings("unchecked") final TypeMapper parameterMapper = (TypeMapper)mappers[i]; @@ -119,8 +155,8 @@ abstract class AbstractMethodImplementation protected TypeMapper[] getParameterMappers() { TypeMapper[] mappers = parameterMappers; - - if(mappers == null) { + + if (mappers == null) { mappers = getParameterTypeMappers(query, null); parameterMappers = mappers; } @@ -131,14 +167,14 @@ abstract class AbstractMethodImplementation protected void setParameterMappers(final Class[] customTypeMapperClasses) { parameterMappers = getParameterTypeMappers(query, customTypeMapperClasses); } - + protected static TypeMapper[] getParameterTypeMappers(final Query query, final Class[] customTypeMapperClasses) { final int parameters = query.getParameterCount(); final TypeMapper[] mappers = new TypeMapper[parameters]; final Map knownMappers = QueryTool.getTypeMap(); - for(int i = 0; i < parameters; i++) { + for (int i = 0; i < parameters; i++) { final Constructor customMapperConstructor = getConstructor(customTypeMapperClasses, i); if (customMapperConstructor != null) { @@ -146,19 +182,19 @@ abstract class AbstractMethodImplementation mappers[i] = customMapperConstructor.newInstance(); } catch (Exception ex) { throw new InvalidQueryException("Cannot construct custom type mapper " - + customMapperConstructor.getName(), ex); + + customMapperConstructor.getName(), ex); } } else { final TypeMapper mapper = knownMappers.get(query.getParameterType(i)); - if(mapper != null) { - mappers[i] = mapper; - } else { + if (mapper != null) { + mappers[i] = mapper; + } else { throw new InvalidQueryException("Unknown primitive type: " + query.getParameterType(i).getName()); + } } } - } return mappers; } diff --git a/eodsql/src/net/lemnik/eodsql/impl/SelectDynamicParameterFactory.java b/eodsql/src/net/lemnik/eodsql/impl/SelectDynamicParameterFactory.java index 7fa128f..7a1e222 100644 --- a/eodsql/src/net/lemnik/eodsql/impl/SelectDynamicParameterFactory.java +++ b/eodsql/src/net/lemnik/eodsql/impl/SelectDynamicParameterFactory.java @@ -149,6 +149,14 @@ class SelectDynamicParameterFactory implements DynamicParameterFactory { return new Class[0]; } + public int timeout() { + return 0; + } + + public int timeoutParameterIndex() { + return 0; + } + @Override public int hashCode() { int hash = "Select".hashCode(); diff --git a/eodsql/src/net/lemnik/eodsql/impl/UpdateDynamicParameterFactory.java b/eodsql/src/net/lemnik/eodsql/impl/UpdateDynamicParameterFactory.java index 349d82e..0b58e41 100644 --- a/eodsql/src/net/lemnik/eodsql/impl/UpdateDynamicParameterFactory.java +++ b/eodsql/src/net/lemnik/eodsql/impl/UpdateDynamicParameterFactory.java @@ -69,6 +69,14 @@ class UpdateDynamicParameterFactory implements DynamicParameterFactory { return Update.class; } + public int timeout() { + return 0; + } + + public int timeoutParameterIndex() { + return 0; + } + @Override public int hashCode() { int hash = "Update".hashCode(); diff --git a/eodsql/test/net/lemnik/eodsql/PrimitiveQueryWithTimeout.java b/eodsql/test/net/lemnik/eodsql/PrimitiveQueryWithTimeout.java new file mode 100644 index 0000000..1bf6d99 --- /dev/null +++ b/eodsql/test/net/lemnik/eodsql/PrimitiveQueryWithTimeout.java @@ -0,0 +1,57 @@ +/* + * PrimitiveQuery.java + * + * Created on April 6, 2007, 11:05 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ +package net.lemnik.eodsql; + +import java.sql.SQLException; +import java.util.List; + +/** + * + * @author jason + */ +public interface PrimitiveQueryWithTimeout extends BaseQuery { + + @Update( + sql = "INSERT INTO primitive_table (my_integer, my_double) VALUES (?1, ?2)", timeout = 1) + public void insert(Integer intValue, Double doubleValue); + + @Select(sql = "SELECT MAX(my_integer) FROM primitive_table", timeout = 1) + public int maxInt(); + + @Select(sql = "SELECT MAX(my_integer) FROM primitive_table", timeoutParameterIndex = 1) + public int maxInt(int timeout); + + @Select(sql = "SELECT MIN(my_integer) FROM primitive_table", timeout = 1) + public int minInt(); + + @Select(sql = "SELECT MIN(my_integer) FROM primitive_table", timeoutParameterIndex = 1) + public int minInt(Integer timeout); + + @Select(sql = "SELECT AVG(my_double) FROM primitive_table", timeout = 1) + public double averageDouble(); + + @Select(sql = "SELECT AVG(my_double) FROM primitive_table", timeout = 1, timeoutParameterIndex = 1) + public double averageDouble(Integer timeout); + + @Select(sql = "SELECT my_integer FROM primitive_table ORDER BY my_integer ASC", timeout = 1) + public int[] sortedInts(); + + @Select(sql = "SELECT my_integer FROM primitive_table ORDER BY my_integer ASC", timeout = 1) + public List sortedIntList(); + + @Update(sql = "DELETE FROM primitive_table", timeout = 1) + public void deleteAll(); + + @Update(sql = "CREATE TABLE primitive_table (my_integer INTEGER, my_double DOUBLE)", timeout = 1) + public void createPrimitiveTable(); + + @Update(sql = "DROP TABLE primitive_table", timeout = 1) + public void dropPrimitiveTable() throws SQLException; + +} diff --git a/eodsql/test/net/lemnik/eodsql/SetTimeoutTest.java b/eodsql/test/net/lemnik/eodsql/SetTimeoutTest.java new file mode 100644 index 0000000..b85370d --- /dev/null +++ b/eodsql/test/net/lemnik/eodsql/SetTimeoutTest.java @@ -0,0 +1,161 @@ +package net.lemnik.eodsql; + +import java.util.List; +import java.util.Random; + +/** + * + * @author jason + */ +public class SetTimeoutTest extends EoDTestCase { + + private Data[] allData; + + private int maxInt = Integer.MIN_VALUE; + + private int minInt = Integer.MAX_VALUE; + + private double avgDouble = 0.0; + + PrimitiveQueryWithTimeout query = null; + + public SetTimeoutTest(String testName) { + super(testName); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + query = QueryTool.getQuery(getConnection(), PrimitiveQueryWithTimeout.class); + query.createPrimitiveTable(); + + createData(query); + } + + public void testMinInt() throws Throwable { + assertEquals(this.minInt, query.minInt()); + } + + public void testMinIntWithCustomTimeoutNull() throws Throwable { + assertEquals(this.minInt, query.minInt(null)); + } + + public void testMinIntWithCustomTimeout0() throws Throwable { + assertEquals(this.minInt, query.minInt(0)); + } + + public void testMinIntWithCustomTimeout3() throws Throwable { + assertEquals(this.minInt, query.minInt(3)); + } + + public void testMaxInt() throws Exception { + assertEquals(this.maxInt, query.maxInt()); + } + + public void testMaxIntCustomTimeout() throws Exception { + assertEquals(this.maxInt, query.maxInt(2)); + } + + public void testAvgDouble() throws Exception { + assertEquals(this.avgDouble, query.averageDouble()); + } + + public void testAvgDoubleCustomTimeoutNull() throws Exception { + assertEquals(this.avgDouble, query.averageDouble(null)); + } + + public void testAvgDoubleCustomTimeout0() throws Exception { + assertEquals(this.avgDouble, query.averageDouble(0)); + } + + public void testAvgDoubleCustomTimeout5() throws Exception { + assertEquals(this.avgDouble, query.averageDouble(5)); + } + + public void testEmptyArray() throws Exception { + query.deleteAll(); + + final int[] array = query.sortedInts(); + + assertNotNull(array); + assertEquals(0, array.length); + } + + public void testPrimitiveListWithNull() throws Exception { + query.deleteAll(); + query.insert(17, 3.0); + query.insert(-1, 2.0); + query.insert(null, 1.0); + + final List numbers = query.sortedIntList(); + + assertNotNull(numbers); + assertEquals(numbers.size(), 3); + + assertEquals(null, numbers.get(0)); + assertEquals(Integer.valueOf(-1), numbers.get(1)); + assertEquals(Integer.valueOf(17), numbers.get(2)); + } + + public void testPrimitiveArrayWithNull() throws Exception { + query.deleteAll(); + query.insert(17, 3.0); + query.insert(-1, 2.0); + query.insert(null, 1.0); + + final int[] numbers = query.sortedInts(); + + assertNotNull(numbers); + assertEquals(numbers.length, 3); + + assertEquals(0, numbers[0]); + assertEquals(-1, numbers[1]); + assertEquals(17, numbers[2]); + } + + @Override + protected void tearDown() throws Exception { + query.dropPrimitiveTable(); + query = null; + + super.tearDown(); + } + + private void createData(PrimitiveQueryWithTimeout query) { + double doubleValues = 0.0; + allData = new Data[100]; + + Random random = new Random(); + + for(int i = 0; i < allData.length; i++) { + allData[i] = new Data(random.nextInt(), random.nextDouble()); + query.insert(allData[i].intValue, allData[i].doubleValue); + + if(allData[i].intValue < minInt) { + minInt = allData[i].intValue; + } + + if(allData[i].intValue > maxInt) { + maxInt = allData[i].intValue; + } + + doubleValues += allData[i].doubleValue; + } + + avgDouble = doubleValues / allData.length; + } + + private static class Data { + + public int intValue; + + public double doubleValue; + + public Data(int intValue, double doubleValue) { + this.intValue = intValue; + this.doubleValue = doubleValue; + } + + } +} -- 1.8.0