View Javadoc
1   /*
2    *    Copyright 2009-2023 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    *       https://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    @Test
66    void shouldNotCallNextOnClosedResultSet_SimpleResult() throws Exception {
67      final Configuration config = new Configuration();
68      final TypeHandlerRegistry registry = config.getTypeHandlerRegistry();
69      final MappedStatement ms = new MappedStatement.Builder(config, "testSelect",
70          new StaticSqlSource(config, "some select statement"), SqlCommandType.SELECT)
71              .resultMaps(new ArrayList<ResultMap>() {
72                private static final long serialVersionUID = 1L;
73                {
74                  add(new ResultMap.Builder(config, "testMap", HashMap.class, new ArrayList<ResultMapping>() {
75                    private static final long serialVersionUID = 1L;
76                    {
77                      add(new ResultMapping.Builder(config, "id", "id", registry.getTypeHandler(Integer.class)).build());
78                    }
79                  }).build());
80                }
81              }).build();
82  
83      final Executor executor = null;
84      final ParameterHandler parameterHandler = null;
85      final ResultHandler<?> resultHandler = null;
86      final BoundSql boundSql = null;
87      final RowBounds rowBounds = new RowBounds(5, 1);
88      final DefaultResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, ms, parameterHandler,
89          resultHandler, boundSql, rowBounds);
90  
91      when(stmt.getResultSet()).thenReturn(rs);
92      when(rsmd.getColumnCount()).thenReturn(1);
93      when(rsmd.getColumnLabel(1)).thenReturn("id");
94      when(rsmd.getColumnType(1)).thenReturn(Types.INTEGER);
95      when(rsmd.getColumnClassName(1)).thenReturn(Integer.class.getCanonicalName());
96      when(stmt.getConnection()).thenReturn(conn);
97      when(conn.getMetaData()).thenReturn(dbmd);
98      when(dbmd.supportsMultipleResultSets()).thenReturn(false); // for simplicity.
99  
100     final List<Object> results = resultSetHandler.handleResultSets(stmt);
101     assertEquals(0, results.size());
102   }
103 
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           private static final long serialVersionUID = 1L;
111           {
112             add(new ResultMapping.Builder(config, "role", "role", registry.getTypeHandler(String.class)).build());
113           }
114         }).build();
115     config.addResultMap(nestedResultMap);
116     final MappedStatement ms = new MappedStatement.Builder(config, "selectPerson",
117         new StaticSqlSource(config, "select person..."), SqlCommandType.SELECT).resultMaps(new ArrayList<ResultMap>() {
118           private static final long serialVersionUID = 1L;
119           {
120             add(new ResultMap.Builder(config, "personMap", HashMap.class, new ArrayList<ResultMapping>() {
121               private static final long serialVersionUID = 1L;
122               {
123                 add(new ResultMapping.Builder(config, "id", "id", registry.getTypeHandler(Integer.class)).build());
124                 add(new ResultMapping.Builder(config, "roles").nestedResultMapId("roleMap").build());
125               }
126             }).build());
127           }
128         }).resultOrdered(true).build();
129 
130     final Executor executor = null;
131     final ParameterHandler parameterHandler = null;
132     final ResultHandler<?> resultHandler = null;
133     final BoundSql boundSql = null;
134     final RowBounds rowBounds = new RowBounds(5, 1);
135     final DefaultResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, ms, parameterHandler,
136         resultHandler, boundSql, rowBounds);
137 
138     when(stmt.getResultSet()).thenReturn(rs);
139     when(rsmd.getColumnCount()).thenReturn(2);
140     when(rsmd.getColumnLabel(1)).thenReturn("id");
141     when(rsmd.getColumnType(1)).thenReturn(Types.INTEGER);
142     when(rsmd.getColumnClassName(1)).thenReturn(Integer.class.getCanonicalName());
143 
144     final List<Object> results = resultSetHandler.handleResultSets(stmt);
145     assertEquals(0, results.size());
146   }
147 
148   /*
149    * Simulate a driver that closes ResultSet automatically when next() returns false (e.g. DB2).
150    */
151   protected abstract class ImpatientResultSet implements ResultSet {
152     private int rowIndex = -1;
153     private List<Map<String, Object>> rows = new ArrayList<>();
154 
155     protected ImpatientResultSet() {
156       Map<String, Object> row = new HashMap<>();
157       row.put("id", 1);
158       row.put("role", "CEO");
159       rows.add(row);
160     }
161 
162     @Override
163     public boolean next() throws SQLException {
164       throwIfClosed();
165       return ++rowIndex < rows.size();
166     }
167 
168     @Override
169     public boolean isClosed() {
170       return rowIndex >= rows.size();
171     }
172 
173     @Override
174     public String getString(String columnLabel) throws SQLException {
175       throwIfClosed();
176       return (String) rows.get(rowIndex).get(columnLabel);
177     }
178 
179     @Override
180     public int getInt(String columnLabel) throws SQLException {
181       throwIfClosed();
182       return (Integer) rows.get(rowIndex).get(columnLabel);
183     }
184 
185     @Override
186     public boolean wasNull() throws SQLException {
187       throwIfClosed();
188       return false;
189     }
190 
191     @Override
192     public ResultSetMetaData getMetaData() {
193       return rsmd;
194     }
195 
196     @Override
197     public int getType() throws SQLException {
198       throwIfClosed();
199       return ResultSet.TYPE_FORWARD_ONLY;
200     }
201 
202     private void throwIfClosed() throws SQLException {
203       if (rowIndex >= rows.size()) {
204         throw new SQLException("Invalid operation: result set is closed.");
205       }
206     }
207   }
208 }