Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -846,9 +846,11 @@ private String prependPrefix(String columnName, String prefix) {

private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
skipRows(rsw.getResultSet(), rowBounds);
if ( ! rsw.getResultSet().isClosed()) {
skipRows(rsw.getResultSet(), rowBounds);
}
Object rowValue = previousRowValue;
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
while (shouldProcessMoreRows(resultContext, rowBounds) && ! rsw.getResultSet().isClosed() && rsw.getResultSet().next()) {
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
Object partialObject = nestedResultObjects.get(rowKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import java.util.List;

import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.cursor.defaults.DefaultCursor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.parameter.ParameterHandler;
Expand All @@ -42,6 +43,7 @@
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandler;
Expand All @@ -65,6 +67,82 @@ public class DefaultResultSetHandlerTest {
private Connection conn;
@Mock
private DatabaseMetaData dbmd;

/**
* Test the behavior of calling {@link DefaultResultSetHandler#handleRowValues}
* multiple times with a nested mapping that has ordered results.
* This test replicates the behavior of the {@link DefaultCursor}.
*
* Some database drivers (like DB2) do not allow any calls to the {@link ResultSet}
* after the {@link ResultSet#next()} method has returned false. The Javadoc
* mentions that implementations are allowed to throw {@link SQLException} in
* this case. In this test we verify that we do not perform any more calls on
* the {@link ResultSet} instance after the result set is closed when the end
* was reached.
*/
@Test
public void shouldNotCallNextWhenResultSetIsClosedForNestedMapping() throws Exception {

final MappedStatement ms = getNestedAndOrderedMappedStatement();
final ResultMap rm = ms.getResultMaps().get(0);

final Executor executor = null;
final ParameterHandler parameterHandler = null;
final TestResultHandler resultHandler = new TestResultHandler();
final BoundSql boundSql = null;
final RowBounds rowBounds = RowBounds.DEFAULT;

final DefaultResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, ms, parameterHandler, resultHandler, boundSql, rowBounds);

when(stmt.getResultSet()).thenReturn(rs);
when(rs.getMetaData()).thenReturn(rsmd);
when(rs.getType()).thenReturn(ResultSet.TYPE_FORWARD_ONLY);
//Simulate a JDBC driver that throws an SQL exception when the end of the result set was reached
when(rs.next()).thenReturn(true).thenReturn(false).thenThrow(new SQLException("Not allowed to call next() after false was returned from previous invocation!"));
when(rs.getInt("CoLuMn1")).thenReturn(100);
when(rs.getInt("CoLuMn2")).thenReturn(200);
when(rs.wasNull()).thenReturn(false);
when(rsmd.getColumnCount()).thenReturn(2);
when(rsmd.getColumnLabel(1)).thenReturn("CoLuMn1");
when(rsmd.getColumnType(1)).thenReturn(Types.INTEGER);
when(rsmd.getColumnClassName(1)).thenReturn(Integer.class.getCanonicalName());
when(rsmd.getColumnLabel(2)).thenReturn("CoLuMn2");
when(rsmd.getColumnType(2)).thenReturn(Types.INTEGER);
when(rsmd.getColumnClassName(2)).thenReturn(Integer.class.getCanonicalName());
when(stmt.getConnection()).thenReturn(conn);
when(conn.getMetaData()).thenReturn(dbmd);
when(dbmd.supportsMultipleResultSets()).thenReturn(false); // for simplicity.

final ResultSetWrapper rsw = new ResultSetWrapper(rs, ms.getConfiguration());

//Read the first and only record
resultSetHandler.handleRowValues(rsw, rm, resultHandler, rowBounds, null);
assertEquals(1, resultHandler.getResults().size());

when(rs.isClosed()).thenReturn(true);
when(rs.getType()).thenThrow(new SQLException("No interaction allowed when the result set is closed!"));

//Call handleRowValues a second time like the DefaultCursor does to make sure no more results are present
resultSetHandler.handleRowValues(rsw, rm, resultHandler, rowBounds, null);
assertEquals(1, resultHandler.getResults().size());

assertEquals(100, ((HashMap) resultHandler.getResults().get(0)).get("cOlUmN1"));
assertEquals(200, ((HashMap) ((HashMap) resultHandler.getResults().get(0)).get("nestedData")).get("cOlUmN2"));
}

private static class TestResultHandler implements ResultHandler<Object> {
private final List<Object> results = new ArrayList<>();

@Override
public void handleResult(ResultContext<? extends Object> resultContext) {
results.add(resultContext.getResultObject());
resultContext.stop();
}

public List<Object> getResults() {
return results;
}
}

/**
* Contrary to the spec, some drivers require case-sensitive column names when getting result.
Expand Down Expand Up @@ -143,5 +221,31 @@ MappedStatement getMappedStatement() {
}
}).build();
}

MappedStatement getNestedAndOrderedMappedStatement() {
final Configuration config = new Configuration();
final TypeHandlerRegistry registry = config.getTypeHandlerRegistry();

ResultMap nestedResultMap = new ResultMap.Builder(config, "nestedTestMap", HashMap.class, new ArrayList<ResultMapping>() {
{
add(new ResultMapping.Builder(config, "cOlUmN2", "CoLuMn2", registry.getTypeHandler(Integer.class)).build());
}
}).build();
config.addResultMap(nestedResultMap);

return new MappedStatement.Builder(config, "testSelect", new StaticSqlSource(config, "some select statement"), SqlCommandType.SELECT).resultMaps(
new ArrayList<ResultMap>() {
{
add(new ResultMap.Builder(config, "testMap", HashMap.class, new ArrayList<ResultMapping>() {
{
add(new ResultMapping.Builder(config, "cOlUmN1", "CoLuMn1", registry.getTypeHandler(Integer.class)).build());
add(new ResultMapping.Builder(config, "nestedData").nestedResultMapId("nestedTestMap").build());
}
}).build());
}
})
.resultOrdered(true)
.build();
}

}