View Javadoc
1   /*
2    *    Copyright 2009-2021 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.jupiter.api.Assertions.assertEquals;
19  import static org.mockito.Mockito.when;
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.jupiter.api.Test;
46  import org.junit.jupiter.api.extension.ExtendWith;
47  import org.mockito.Mock;
48  import org.mockito.Spy;
49  import org.mockito.junit.jupiter.MockitoExtension;
50  
51  @ExtendWith(MockitoExtension.class)
52  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    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   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 
147     final List<Object> results = resultSetHandler.handleResultSets(stmt);
148     assertEquals(0, results.size());
149   }
150 
151   /*
152    * Simulate a driver that closes ResultSet automatically when next() returns false (e.g. DB2).
153    */
154   protected abstract class ImpatientResultSet implements ResultSet {
155     private int rowIndex = -1;
156     private List<Map<String, Object>> rows = new ArrayList<>();
157 
158     protected ImpatientResultSet() {
159       Map<String, Object> row = new HashMap<>();
160       row.put("id", 1);
161       row.put("role", "CEO");
162       rows.add(row);
163     }
164 
165     @Override
166     public boolean next() throws SQLException {
167       throwIfClosed();
168       return ++rowIndex < rows.size();
169     }
170 
171     @Override
172     public boolean isClosed() {
173       return rowIndex >= rows.size();
174     }
175 
176     @Override
177     public String getString(String columnLabel) throws SQLException {
178       throwIfClosed();
179       return (String) rows.get(rowIndex).get(columnLabel);
180     }
181 
182     @Override
183     public int getInt(String columnLabel) throws SQLException {
184       throwIfClosed();
185       return (Integer) rows.get(rowIndex).get(columnLabel);
186     }
187 
188     @Override
189     public boolean wasNull() throws SQLException {
190       throwIfClosed();
191       return false;
192     }
193 
194     @Override
195     public ResultSetMetaData getMetaData() {
196       return rsmd;
197     }
198 
199     @Override
200     public int getType() throws SQLException {
201       throwIfClosed();
202       return ResultSet.TYPE_FORWARD_ONLY;
203     }
204 
205     private void throwIfClosed() throws SQLException {
206       if (rowIndex >= rows.size()) {
207         throw new SQLException("Invalid operation: result set is closed.");
208       }
209     }
210   }
211 }