Skip to content

Commit d14c67e

Browse files
committed
RowBounds with out-of-range offset causes SQLException on DB2
Reported on the mailing list. https://groups.google.com/d/msg/mybatis-user/21UM4I7gm48/CRJShn2iBgAJ
1 parent 4391ad9 commit d14c67e

File tree

2 files changed

+232
-11
lines changed

2 files changed

+232
-11
lines changed

src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -347,11 +347,12 @@ protected void checkResultHandler() {
347347
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
348348
throws SQLException {
349349
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
350-
skipRows(rsw.getResultSet(), rowBounds);
351-
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
352-
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
350+
ResultSet resultSet = rsw.getResultSet();
351+
skipRows(resultSet, rowBounds);
352+
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
353+
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
353354
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
354-
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
355+
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
355356
}
356357
}
357358

@@ -380,7 +381,9 @@ private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
380381
}
381382
} else {
382383
for (int i = 0; i < rowBounds.getOffset(); i++) {
383-
rs.next();
384+
if (!rs.next()) {
385+
break;
386+
}
384387
}
385388
}
386389
}
@@ -846,28 +849,29 @@ private String prependPrefix(String columnName, String prefix) {
846849

847850
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
848851
final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
849-
skipRows(rsw.getResultSet(), rowBounds);
852+
ResultSet resultSet = rsw.getResultSet();
853+
skipRows(resultSet, rowBounds);
850854
Object rowValue = previousRowValue;
851-
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
852-
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
855+
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
856+
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
853857
final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
854858
Object partialObject = nestedResultObjects.get(rowKey);
855859
// issue #577 && #542
856860
if (mappedStatement.isResultOrdered()) {
857861
if (partialObject == null && rowValue != null) {
858862
nestedResultObjects.clear();
859-
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
863+
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
860864
}
861865
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
862866
} else {
863867
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
864868
if (partialObject == null) {
865-
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
869+
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
866870
}
867871
}
868872
}
869873
if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
870-
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
874+
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
871875
previousRowValue = null;
872876
} else if (rowValue != null) {
873877
previousRowValue = rowValue;
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/**
2+
* Copyright 2009-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.apache.ibatis.executor.resultset;
17+
18+
import static org.junit.Assert.*;
19+
import static org.mockito.Mockito.*;
20+
21+
import java.sql.Connection;
22+
import java.sql.DatabaseMetaData;
23+
import java.sql.ResultSet;
24+
import java.sql.ResultSetMetaData;
25+
import java.sql.SQLException;
26+
import java.sql.Statement;
27+
import java.sql.Types;
28+
import java.util.ArrayList;
29+
import java.util.HashMap;
30+
import java.util.List;
31+
import java.util.Map;
32+
33+
import org.apache.ibatis.builder.StaticSqlSource;
34+
import org.apache.ibatis.executor.Executor;
35+
import org.apache.ibatis.executor.parameter.ParameterHandler;
36+
import org.apache.ibatis.mapping.BoundSql;
37+
import org.apache.ibatis.mapping.MappedStatement;
38+
import org.apache.ibatis.mapping.ResultMap;
39+
import org.apache.ibatis.mapping.ResultMapping;
40+
import org.apache.ibatis.mapping.SqlCommandType;
41+
import org.apache.ibatis.session.Configuration;
42+
import org.apache.ibatis.session.ResultHandler;
43+
import org.apache.ibatis.session.RowBounds;
44+
import org.apache.ibatis.type.TypeHandlerRegistry;
45+
import org.junit.Test;
46+
import org.junit.runner.RunWith;
47+
import org.mockito.Mock;
48+
import org.mockito.Spy;
49+
import org.mockito.junit.MockitoJUnitRunner;
50+
51+
@RunWith(MockitoJUnitRunner.class)
52+
public class DefaultResultSetHandlerTest2 {
53+
54+
@Spy
55+
private ImpatientResultSet rs;
56+
@Mock
57+
private Statement stmt;
58+
@Mock
59+
protected ResultSetMetaData rsmd;
60+
@Mock
61+
private Connection conn;
62+
@Mock
63+
private DatabaseMetaData dbmd;
64+
65+
@SuppressWarnings("serial")
66+
@Test
67+
public void shouldNotCallNextOnClosedResultSet_SimpleResult() throws Exception {
68+
final Configuration config = new Configuration();
69+
final TypeHandlerRegistry registry = config.getTypeHandlerRegistry();
70+
final MappedStatement ms = new MappedStatement.Builder(config, "testSelect",
71+
new StaticSqlSource(config, "some select statement"), SqlCommandType.SELECT).resultMaps(
72+
new ArrayList<ResultMap>() {
73+
{
74+
add(new ResultMap.Builder(config, "testMap", HashMap.class, new ArrayList<ResultMapping>() {
75+
{
76+
add(new ResultMapping.Builder(config, "id", "id", registry.getTypeHandler(Integer.class)).build());
77+
}
78+
}).build());
79+
}
80+
}).build();
81+
82+
final Executor executor = null;
83+
final ParameterHandler parameterHandler = null;
84+
final ResultHandler<?> resultHandler = null;
85+
final BoundSql boundSql = null;
86+
final RowBounds rowBounds = new RowBounds(5, 1);
87+
final DefaultResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, ms, parameterHandler,
88+
resultHandler, boundSql, rowBounds);
89+
90+
when(stmt.getResultSet()).thenReturn(rs);
91+
when(rsmd.getColumnCount()).thenReturn(1);
92+
when(rsmd.getColumnLabel(1)).thenReturn("id");
93+
when(rsmd.getColumnType(1)).thenReturn(Types.INTEGER);
94+
when(rsmd.getColumnClassName(1)).thenReturn(Integer.class.getCanonicalName());
95+
when(stmt.getConnection()).thenReturn(conn);
96+
when(conn.getMetaData()).thenReturn(dbmd);
97+
when(dbmd.supportsMultipleResultSets()).thenReturn(false); // for simplicity.
98+
99+
final List<Object> results = resultSetHandler.handleResultSets(stmt);
100+
assertEquals(0, results.size());
101+
}
102+
103+
@SuppressWarnings("serial")
104+
@Test
105+
public void shouldNotCallNextOnClosedResultSet_NestedResult() throws Exception {
106+
final Configuration config = new Configuration();
107+
final TypeHandlerRegistry registry = config.getTypeHandlerRegistry();
108+
final ResultMap nestedResultMap = new ResultMap.Builder(config, "roleMap", HashMap.class,
109+
new ArrayList<ResultMapping>() {
110+
{
111+
add(new ResultMapping.Builder(config, "role", "role", registry.getTypeHandler(String.class))
112+
.build());
113+
}
114+
}).build();
115+
config.addResultMap(nestedResultMap);
116+
final MappedStatement ms = new MappedStatement.Builder(config, "selectPerson",
117+
new StaticSqlSource(config, "select person..."),
118+
SqlCommandType.SELECT).resultMaps(
119+
new ArrayList<ResultMap>() {
120+
{
121+
add(new ResultMap.Builder(config, "personMap", HashMap.class, new ArrayList<ResultMapping>() {
122+
{
123+
add(new ResultMapping.Builder(config, "id", "id", registry.getTypeHandler(Integer.class))
124+
.build());
125+
add(new ResultMapping.Builder(config, "roles").nestedResultMapId("roleMap").build());
126+
}
127+
}).build());
128+
}
129+
})
130+
.resultOrdered(true)
131+
.build();
132+
133+
final Executor executor = null;
134+
final ParameterHandler parameterHandler = null;
135+
final ResultHandler<?> resultHandler = null;
136+
final BoundSql boundSql = null;
137+
final RowBounds rowBounds = new RowBounds(5, 1);
138+
final DefaultResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, ms, parameterHandler,
139+
resultHandler, boundSql, rowBounds);
140+
141+
when(stmt.getResultSet()).thenReturn(rs);
142+
when(rsmd.getColumnCount()).thenReturn(2);
143+
when(rsmd.getColumnLabel(1)).thenReturn("id");
144+
when(rsmd.getColumnType(1)).thenReturn(Types.INTEGER);
145+
when(rsmd.getColumnClassName(1)).thenReturn(Integer.class.getCanonicalName());
146+
when(rsmd.getColumnLabel(2)).thenReturn("role");
147+
when(rsmd.getColumnType(2)).thenReturn(Types.VARCHAR);
148+
when(rsmd.getColumnClassName(2)).thenReturn(String.class.getCanonicalName());
149+
when(stmt.getConnection()).thenReturn(conn);
150+
when(conn.getMetaData()).thenReturn(dbmd);
151+
when(dbmd.supportsMultipleResultSets()).thenReturn(false); // for simplicity.
152+
153+
final List<Object> results = resultSetHandler.handleResultSets(stmt);
154+
assertEquals(0, results.size());
155+
}
156+
157+
/*
158+
* Simulate a driver that closes ResultSet automatically when next() returns false (e.g. DB2).
159+
*/
160+
protected abstract class ImpatientResultSet implements ResultSet {
161+
private int rowIndex = -1;
162+
private List<Map<String, Object>> rows = new ArrayList<>();
163+
164+
protected ImpatientResultSet() {
165+
Map<String, Object> row = new HashMap<>();
166+
row.put("id", Integer.valueOf(1));
167+
row.put("role", "CEO");
168+
rows.add(row);
169+
}
170+
171+
@Override
172+
public boolean next() throws SQLException {
173+
throwIfClosed();
174+
return ++rowIndex < rows.size();
175+
}
176+
177+
@Override
178+
public boolean isClosed() throws SQLException {
179+
return rowIndex >= rows.size();
180+
}
181+
182+
@Override
183+
public String getString(String columnLabel) throws SQLException {
184+
throwIfClosed();
185+
return (String) rows.get(rowIndex).get(columnLabel);
186+
}
187+
188+
@Override
189+
public int getInt(String columnLabel) throws SQLException {
190+
throwIfClosed();
191+
return (Integer) rows.get(rowIndex).get(columnLabel);
192+
}
193+
194+
@Override
195+
public boolean wasNull() throws SQLException {
196+
throwIfClosed();
197+
return false;
198+
}
199+
200+
@Override
201+
public ResultSetMetaData getMetaData() throws SQLException {
202+
return rsmd;
203+
}
204+
205+
@Override
206+
public int getType() throws SQLException {
207+
throwIfClosed();
208+
return ResultSet.TYPE_FORWARD_ONLY;
209+
}
210+
211+
private void throwIfClosed() throws SQLException {
212+
if (rowIndex >= rows.size()) {
213+
throw new SQLException("Invalid operation: result set is closed.");
214+
}
215+
}
216+
}
217+
}

0 commit comments

Comments
 (0)