Index: src/main/groovy/sql/Sql.java =================================================================== --- src/main/groovy/sql/Sql.java (revision 17235) +++ src/main/groovy/sql/Sql.java (working copy) @@ -66,7 +66,7 @@ /** * Hook to allow derived classes to access the log */ - protected Logger log = Logger.getLogger(getClass().getName()); + protected static Logger log = Logger.getLogger(Sql.class.getName()); private DataSource dataSource; @@ -457,6 +457,8 @@ return Types.VARCHAR; } }; + + private static final List EMPTY_LIST = Collections.emptyList(); public static InParameter ARRAY(Object value) { return in(Types.ARRAY, value); @@ -693,7 +695,7 @@ return new DataSet(this, table); } - public DataSet dataSet(Class type) { + public DataSet dataSet(Class type) { return new DataSet(this, type); } @@ -738,7 +740,7 @@ * @param closure called for each row with a GroovyResultSet * @throws SQLException if a database access error occurs */ - public void query(String sql, List params, Closure closure) throws SQLException { + public void query(String sql, List params, Closure closure) throws SQLException { Connection connection = createConnection(); PreparedStatement statement = null; ResultSet results = null; @@ -765,7 +767,7 @@ * @throws SQLException if a database access error occurs */ public void query(GString gstring, Closure closure) throws SQLException { - List params = getParameters(gstring); + List params = getParameters(gstring); String sql = asSql(gstring, params); query(sql, params, closure); } @@ -820,7 +822,7 @@ * @param closure called for each row with a GroovyResultSet * @throws SQLException if a database access error occurs */ - public void eachRow(String sql, List params, Closure closure) throws SQLException { + public void eachRow(String sql, List params, Closure closure) throws SQLException { Connection connection = createConnection(); PreparedStatement statement = null; ResultSet results = null; @@ -851,7 +853,7 @@ * @throws SQLException if a database access error occurs */ public void eachRow(GString gstring, Closure closure) throws SQLException { - List params = getParameters(gstring); + List params = getParameters(gstring); String sql = asSql(gstring, params); eachRow(sql, params, closure); } @@ -863,7 +865,7 @@ * @return a list of GroovyRowResult objects * @throws SQLException if a database access error occurs */ - public List rows(String sql) throws SQLException { + public List rows(String sql) throws SQLException { return rows(sql, (Closure) null); } @@ -874,8 +876,8 @@ * @return a list of GroovyRowResult objects * @throws SQLException if a database access error occurs */ - public List rows(GString gstring) throws SQLException { - List params = getParameters(gstring); + public List rows(GString gstring) throws SQLException { + List params = getParameters(gstring); String sql = asSql(gstring, params); return rows(sql, params); } @@ -888,61 +890,60 @@ * @return a list of GroovyRowResult objects * @throws SQLException if a database access error occurs */ - public List rows(String sql, Closure metaClosure) throws SQLException { - List results = new ArrayList(); - Connection connection = createConnection(); - Statement statement = getStatement(connection, sql); - ResultSet rs = null; - try { - log.fine(sql); - rs = statement.executeQuery(sql); - if (metaClosure != null) metaClosure.call(rs.getMetaData()); - - while (rs.next()) { - results.add(SqlGroovyMethods.toRowResult(rs)); - } - return (results); - } catch (SQLException e) { - log.log(Level.FINE, "Failed to execute: " + sql, e); - throw e; - } finally { - closeResources(connection, statement, rs); - } + public List rows(String sql, Closure metaClosure) throws SQLException { + ResultSet rs = executeQuery(sql); + if (metaClosure != null) metaClosure.call(rs.getMetaData()); + return asList(sql, rs); } - /** - * Performs the given SQL query with the list of params and return - * the rows of the result set. - * - * @param sql the SQL statement - * @param params a list of parameters - * @return a list of GroovyRowResult objects - * @throws SQLException if a database access error occurs - */ - public List rows(String sql, List params) throws SQLException { - List results = new ArrayList(); - Connection connection = createConnection(); - PreparedStatement statement = null; - ResultSet rs = null; - try { - log.fine(sql); - statement = getPreparedStatement(connection, sql, params); - rs = statement.executeQuery(); - while (rs.next()) { - results.add(SqlGroovyMethods.toRowResult(rs)); - } - return (results); - } - catch (SQLException e) { - log.log(Level.FINE, "Failed to execute: " + sql, e); - throw e; - } - finally { - closeResources(connection, statement, rs); - } - } + protected final ResultSet executeQuery(String sql) throws SQLException { + return new QueryCommand(sql).execute(); + } - /** + /** + * Performs the given SQL query with the list of params and return the rows + * of the result set. + * + * @param sql + * the SQL statement + * @param params + * a list of parameters + * @return a list of GroovyRowResult objects + * @throws SQLException + * if a database access error occurs + */ + public List rows(String sql, List params) + throws SQLException { + ResultSet rs = executePreparedQuery(sql, params); + return asList(sql, rs); + } + + protected final ResultSet executePreparedQuery(String sql, List params) + throws SQLException { + return new PreparedQueryCommand(sql, params).execute(); + } + + public static List resultSetAsList(String sql, ResultSet rs) throws SQLException { + List results = new ArrayList(); + + try { + while (rs.next()) { + results.add(SqlGroovyMethods.toRowResult(rs)); + } + return (results); + } catch (SQLException e) { + log.log(Level.INFO, "Failed to retrieve row from ResultSet for: " + sql, e); + throw e; + } finally { + rs.close(); + } + } + + protected List asList(String sql, ResultSet rs) throws SQLException { + return resultSetAsList(sql, rs); + } + + /** * Performs the given SQL query and return the first row of the result set. * * @param sql the SQL statement @@ -950,7 +951,7 @@ * @throws SQLException if a database access error occurs */ public Object firstRow(String sql) throws SQLException { - List rows = rows(sql); + List rows = rows(sql); if (rows.isEmpty()) return null; return (rows.get(0)); } @@ -964,7 +965,7 @@ * @throws SQLException if a database access error occurs */ public Object firstRow(GString gstring) throws SQLException { - List params = getParameters(gstring); + List params = getParameters(gstring); String sql = asSql(gstring, params); return firstRow(sql, params); } @@ -978,8 +979,8 @@ * @return a GroovyRowResult object or null if no row is found * @throws SQLException if a database access error occurs */ - public Object firstRow(String sql, List params) throws SQLException { - List rows = rows(sql, params); + public Object firstRow(String sql, List params) throws SQLException { + List rows = rows(sql, params); if (rows.isEmpty()) return null; return rows.get(0); } @@ -1022,7 +1023,7 @@ * no results * @throws SQLException if a database access error occurs */ - public boolean execute(String sql, List params) throws SQLException { + public boolean execute(String sql, List params) throws SQLException { Connection connection = createConnection(); PreparedStatement statement = null; try { @@ -1051,7 +1052,7 @@ * @throws SQLException if a database access error occurs */ public boolean execute(GString gstring) throws SQLException { - List params = getParameters(gstring); + List params = getParameters(gstring); String sql = asSql(gstring, params); return execute(sql, params); } @@ -1065,7 +1066,7 @@ * inserted row * @throws SQLException if a database access error occurs */ - public List executeInsert(String sql) throws SQLException { + public List> executeInsert(String sql) throws SQLException { Connection connection = createConnection(); Statement statement = null; try { @@ -1075,13 +1076,13 @@ // Prepare a list to contain the auto-generated column // values, and then fetch them from the statement. - List autoKeys = new ArrayList(); + List> autoKeys = new ArrayList>(); ResultSet keys = statement.getGeneratedKeys(); int count = keys.getMetaData().getColumnCount(); // Copy the column values into a list of a list. while (keys.next()) { - List rowKeys = new ArrayList(count); + List rowKeys = new ArrayList(count); for (int i = 1; i <= count; i++) { rowKeys.add(keys.getObject(i)); } @@ -1116,7 +1117,7 @@ * inserted row * @throws SQLException if a database access error occurs */ - public List executeInsert(String sql, List params) throws SQLException { + public List> executeInsert(String sql, List params) throws SQLException { // Now send the SQL to the database. Connection connection = createConnection(); PreparedStatement statement = null; @@ -1129,13 +1130,13 @@ // Prepare a list to contain the auto-generated column // values, and then fetch them from the statement. - List autoKeys = new ArrayList(); + List> autoKeys = new ArrayList>(); ResultSet keys = statement.getGeneratedKeys(); int count = keys.getMetaData().getColumnCount(); // Copy the column values into a list of a list. while (keys.next()) { - List rowKeys = new ArrayList(count); + List rowKeys = new ArrayList(count); for (int i = 1; i <= count; i++) { rowKeys.add(keys.getObject(i)); } @@ -1189,8 +1190,8 @@ * auto-generated keys * @throws SQLException if a database access error occurs */ - public List executeInsert(GString gstring) throws SQLException { - List params = getParameters(gstring); + public List> executeInsert(GString gstring) throws SQLException { + List params = getParameters(gstring); String sql = asSql(gstring, params); return executeInsert(sql, params); } @@ -1228,7 +1229,7 @@ * @return the number of rows updated or 0 for SQL statements that return nothing * @throws SQLException if a database access error occurs */ - public int executeUpdate(String sql, List params) throws SQLException { + public int executeUpdate(String sql, List params) throws SQLException { Connection connection = createConnection(); PreparedStatement statement = null; try { @@ -1254,7 +1255,7 @@ * @throws SQLException if a database access error occurs */ public int executeUpdate(GString gstring) throws SQLException { - List params = getParameters(gstring); + List params = getParameters(gstring); String sql = asSql(gstring, params); return executeUpdate(sql, params); } @@ -1267,7 +1268,7 @@ * @throws SQLException if a database access error occurs */ public int call(String sql) throws Exception { - return call(sql, Collections.EMPTY_LIST); + return call(sql, EMPTY_LIST); } /** @@ -1278,7 +1279,7 @@ * @return the number of rows updated or 0 for SQL statements that return nothing * @throws SQLException if a database access error occurs */ - public int call(String sql, List params) throws Exception { + public int call(String sql, List params) throws Exception { Connection connection = createConnection(); CallableStatement statement = connection.prepareCall(sql); try { @@ -1305,14 +1306,14 @@ * @param closure called for each row with a GroovyResultSet * @throws SQLException if a database access error occurs */ - public void call(String sql, List params, Closure closure) throws Exception { + public void call(String sql, List params, Closure closure) throws Exception { Connection connection = createConnection(); CallableStatement statement = connection.prepareCall(sql); try { log.fine(sql); setParameters(params, statement); statement.execute(); - List results = new ArrayList(); + List results = new ArrayList(); int indx = 0; int inouts = 0; for (Object value : params) { @@ -1348,7 +1349,7 @@ * @throws SQLException if a database access error occurs */ public int call(GString gstring) throws Exception { - List params = getParameters(gstring); + List params = getParameters(gstring); String sql = asSql(gstring, params); return call(sql, params); } @@ -1362,7 +1363,7 @@ * @throws SQLException if a database access error occurs */ public void call(GString gstring, Closure closure) throws Exception { - List params = getParameters(gstring); + List params = getParameters(gstring); String sql = asSql(gstring, params); call(sql, params, closure); } @@ -1482,7 +1483,7 @@ * @return the SQL version of the given query using ? instead of any * parameter */ - protected String asSql(GString gstring, List values) { + protected String asSql(GString gstring, List values) { String[] strings = gstring.getStrings(); if (strings.length <= 0) { throw new IllegalArgumentException("No SQL specified in GString: " + gstring); @@ -1490,7 +1491,7 @@ boolean nulls = false; StringBuffer buffer = new StringBuffer(); boolean warned = false; - Iterator iter = values.iterator(); + Iterator iter = values.iterator(); for (int i = 0; i < strings.length; i++) { String text = strings[i]; if (text != null) { @@ -1607,7 +1608,7 @@ * @param gstring a GString containing the SQL query with embedded params * @return extracts the parameters from the expression as a List */ - protected List getParameters(GString gstring) { + protected List getParameters(GString gstring) { return new ArrayList(Arrays.asList(gstring.getValues())); } @@ -1620,7 +1621,7 @@ * @param statement the statement * @throws SQLException if a database access error occurs */ - protected void setParameters(List params, PreparedStatement statement) throws SQLException { + protected void setParameters(List params, PreparedStatement statement) throws SQLException { int i = 1; for (Object value : params) { setObject(statement, i++, value); @@ -1756,6 +1757,7 @@ * @param statement the statement to configure */ protected void configure(Statement statement) { + Closure configureStatement = this.configureStatement; if (configureStatement != null) { configureStatement.call(statement); } @@ -1983,14 +1985,14 @@ return stmt; } - private PreparedStatement getPreparedStatement(Connection connection, String sql, List params, int returnGeneratedKeys) throws SQLException { + private PreparedStatement getPreparedStatement(Connection connection, String sql, List params, int returnGeneratedKeys) throws SQLException { PreparedStatement statement = (PreparedStatement) getAbstractStatement(new CreatePreparedStatementCommand(returnGeneratedKeys), connection, sql); setParameters(params, statement); configure(statement); return statement; } - private PreparedStatement getPreparedStatement(Connection connection, String sql, List params) throws SQLException { + private PreparedStatement getPreparedStatement(Connection connection, String sql, List params) throws SQLException { return getPreparedStatement(connection, sql, params, 0); } @@ -2032,4 +2034,74 @@ } + private abstract class AbstractQueryCommand { + protected final String sql; + protected Statement statement; + + AbstractQueryCommand(String sql) { + // Don't create statement in subclass constructors, because prefer not to throw in constructors + this.sql = sql; + } + /** + * Execute the command that's defined by the subclass following + * the Command pattern. Specialized parameters are held in the command instances. + * + * @param conn all commands accept a connection + * @return ResultSet from executing a query + */ + final ResultSet execute() throws SQLException { + Connection connection = createConnection(); + Statement statement = null; + try { + log.fine(sql); + // The variation in the patern is isolated + ResultSet result = runQuery(connection); + assert (null != statement); + return result; + } catch (SQLException e) { + log.log(Level.FINE, "Failed to execute: " + sql, e); + throw e; + } finally { + closeResources(connection, statement, null); + } + } + /** + * Perform the query. Must set statement field so that the main ({@link #execute()}) method can clean up. + * This is the method that encloses the variant part of the code. + * @param connection + * @return ResultSet from an executeQuery method. + * @throws SQLException + */ + protected abstract ResultSet runQuery(Connection connection) throws SQLException; + } + + private class PreparedQueryCommand extends AbstractQueryCommand{ + private List params; + + PreparedQueryCommand(String sql, List queryParams) { + super(sql); + params = queryParams; + // TODO Auto-generated constructor stub + } + + @Override + protected ResultSet runQuery(Connection connection) throws SQLException { + PreparedStatement s = getPreparedStatement(connection, sql, params); + statement = s; + return s.executeQuery(); + } + } + + private class QueryCommand extends AbstractQueryCommand{ + + QueryCommand(String sql) { + super(sql); + } + + @Override + protected ResultSet runQuery(Connection connection) throws SQLException { + statement = getStatement(connection, sql); + return statement.executeQuery(sql); + } + } } Index: src/test/groovy/sql/SqlTest.groovy =================================================================== --- src/test/groovy/sql/SqlTest.groovy (revision 17171) +++ src/test/groovy/sql/SqlTest.groovy (working copy) @@ -1,5 +1,7 @@ package groovy.sql +import groovy.sql.GroovyResultSetProxy; + /** * This is more of a sample program than a unit test and is here as an easy * to read demo of GDO. The actual full unit test case is in SqlCompleteTest @@ -97,6 +99,40 @@ } } + void testSubClass() { + def sub = new SqlSubclass(sql) + def res = null + def firstNames = []; + sql.eachRow('select firstname from PERSON') { + firstNames << it.firstname + } + try { + res = sub.rowsCursor('select * from PERSON') + while (res.next()) { + assert firstNames.remove(res.firstname); + } + assert firstNames.isEmpty() + } finally { + if (res) + res.close(); + } + } + + class SqlSubclass extends Sql { + // The constructor was originally public, but should probably be package-private + // it seems unlikely to have an effective use case outside of easier testing + // apps shouldn't go around creating multiple instances of Sql with the same + // datasource/connection. + SqlSubclass(Sql base) { + super(base); + } + + def rowsCursor(String sql) { + def rs = executeQuery(sql); + return new GroovyResultSetProxy(rs).getImpl(); + } + } + protected def createSql() { def ds = new org.hsqldb.jdbc.jdbcDataSource() ds.database = "jdbc:hsqldb:mem:foo" + getMethodName()