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.submitted.cursor_simple;
17  
18  import java.io.IOException;
19  import java.io.Reader;
20  import java.util.ArrayList;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.NoSuchElementException;
24  
25  import org.apache.ibatis.BaseDataTest;
26  import org.apache.ibatis.cursor.Cursor;
27  import org.apache.ibatis.io.Resources;
28  import org.apache.ibatis.session.RowBounds;
29  import org.apache.ibatis.session.SqlSession;
30  import org.apache.ibatis.session.SqlSessionFactory;
31  import org.apache.ibatis.session.SqlSessionFactoryBuilder;
32  import org.junit.jupiter.api.Assertions;
33  import org.junit.jupiter.api.BeforeAll;
34  import org.junit.jupiter.api.Test;
35  
36  class CursorSimpleTest {
37  
38    private static SqlSessionFactory sqlSessionFactory;
39  
40    @BeforeAll
41    static void setUp() throws Exception {
42      // create a SqlSessionFactory
43      try (
44          Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/cursor_simple/mybatis-config.xml")) {
45        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
46      }
47  
48      // populate in-memory database
49      BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
50          "org/apache/ibatis/submitted/cursor_simple/CreateDB.sql");
51    }
52  
53    @Test
54    void shouldGetAllUser() {
55      try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
56        Mapper mapper = sqlSession.getMapper(Mapper.class);
57        Cursor<User> usersCursor = mapper.getAllUsers();
58  
59        Assertions.assertFalse(usersCursor.isOpen());
60  
61        // Cursor is just created, current index is -1
62        Assertions.assertEquals(-1, usersCursor.getCurrentIndex());
63  
64        Iterator<User> iterator = usersCursor.iterator();
65  
66        // Check if hasNext, fetching is started
67        Assertions.assertTrue(iterator.hasNext());
68        Assertions.assertTrue(usersCursor.isOpen());
69        Assertions.assertFalse(usersCursor.isConsumed());
70  
71        // next() has not been called, index is still -1
72        Assertions.assertEquals(-1, usersCursor.getCurrentIndex());
73  
74        User user = iterator.next();
75        Assertions.assertEquals("User1", user.getName());
76        Assertions.assertEquals(0, usersCursor.getCurrentIndex());
77  
78        user = iterator.next();
79        Assertions.assertEquals("User2", user.getName());
80        Assertions.assertEquals(1, usersCursor.getCurrentIndex());
81  
82        user = iterator.next();
83        Assertions.assertEquals("User3", user.getName());
84        Assertions.assertEquals(2, usersCursor.getCurrentIndex());
85  
86        user = iterator.next();
87        Assertions.assertEquals("User4", user.getName());
88        Assertions.assertEquals(3, usersCursor.getCurrentIndex());
89  
90        user = iterator.next();
91        Assertions.assertEquals("User5", user.getName());
92        Assertions.assertEquals(4, usersCursor.getCurrentIndex());
93  
94        // Check no more elements
95        Assertions.assertFalse(iterator.hasNext());
96        Assertions.assertFalse(usersCursor.isOpen());
97        Assertions.assertTrue(usersCursor.isConsumed());
98      }
99    }
100 
101   @Test
102   void testCursorClosedOnSessionClose() {
103     Cursor<User> usersCursor;
104     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
105       Mapper mapper = sqlSession.getMapper(Mapper.class);
106       usersCursor = mapper.getAllUsers();
107 
108       Assertions.assertFalse(usersCursor.isOpen());
109 
110       Iterator<User> iterator = usersCursor.iterator();
111 
112       // Check if hasNext, fetching is started
113       Assertions.assertTrue(iterator.hasNext());
114       Assertions.assertTrue(usersCursor.isOpen());
115       Assertions.assertFalse(usersCursor.isConsumed());
116 
117       // Consume only the first result
118       User user = iterator.next();
119       Assertions.assertEquals("User1", user.getName());
120 
121       // Check there is still remaining elements
122       Assertions.assertTrue(iterator.hasNext());
123       Assertions.assertTrue(usersCursor.isOpen());
124       Assertions.assertFalse(usersCursor.isConsumed());
125     }
126 
127     // The cursor was not fully consumed, but it should be close since we closed the session
128     Assertions.assertFalse(usersCursor.isOpen());
129     Assertions.assertFalse(usersCursor.isConsumed());
130   }
131 
132   @Test
133   void testCursorWithRowBound() {
134     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
135       // RowBound starting at offset 1 and limiting to 2 items
136       Cursor<User> usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(1, 3));
137 
138       Iterator<User> iterator = usersCursor.iterator();
139 
140       User user = iterator.next();
141       Assertions.assertEquals("User2", user.getName());
142       Assertions.assertEquals(1, usersCursor.getCurrentIndex());
143 
144       // Calling hasNext() before next()
145       Assertions.assertTrue(iterator.hasNext());
146       user = iterator.next();
147       Assertions.assertEquals("User3", user.getName());
148       Assertions.assertEquals(2, usersCursor.getCurrentIndex());
149 
150       // Calling next() without a previous hasNext() call
151       user = iterator.next();
152       Assertions.assertEquals("User4", user.getName());
153       Assertions.assertEquals(3, usersCursor.getCurrentIndex());
154 
155       Assertions.assertFalse(iterator.hasNext());
156       Assertions.assertFalse(usersCursor.isOpen());
157       Assertions.assertTrue(usersCursor.isConsumed());
158     }
159   }
160 
161   @Test
162   void testCursorIteratorNoSuchElementExceptionWithHasNext() throws IOException {
163 
164     try (SqlSession sqlSession = sqlSessionFactory.openSession();
165         Cursor<User> usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(1, 1))) {
166       try {
167         Iterator<User> iterator = usersCursor.iterator();
168 
169         User user = iterator.next();
170         Assertions.assertEquals("User2", user.getName());
171         Assertions.assertEquals(1, usersCursor.getCurrentIndex());
172 
173         Assertions.assertFalse(iterator.hasNext());
174         iterator.next();
175         Assertions.fail("We should have failed since we call next() when hasNext() returned false");
176       } catch (NoSuchElementException e) {
177         Assertions.assertFalse(usersCursor.isOpen());
178         Assertions.assertTrue(usersCursor.isConsumed());
179       }
180     }
181   }
182 
183   @Test
184   void testCursorIteratorNoSuchElementExceptionNoHasNext() throws IOException {
185     try (SqlSession sqlSession = sqlSessionFactory.openSession();
186         Cursor<User> usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(1, 1))) {
187       try {
188         Iterator<User> iterator = usersCursor.iterator();
189         User user = iterator.next();
190         Assertions.assertEquals("User2", user.getName());
191         Assertions.assertEquals(1, usersCursor.getCurrentIndex());
192 
193         // Trying next() without hasNext()
194         iterator.next();
195         Assertions.fail("We should have failed since we call next() when is no more items");
196       } catch (NoSuchElementException e) {
197         Assertions.assertFalse(usersCursor.isOpen());
198         Assertions.assertTrue(usersCursor.isConsumed());
199       }
200     }
201   }
202 
203   @Test
204   void testCursorWithBadRowBound() {
205     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
206       // Trying to start at offset 10 (which does not exist, since there is only 4 items)
207       Cursor<User> usersCursor = sqlSession.selectCursor("getAllUsers", null, new RowBounds(10, 2));
208       Iterator<User> iterator = usersCursor.iterator();
209 
210       Assertions.assertFalse(iterator.hasNext());
211       Assertions.assertFalse(usersCursor.isOpen());
212       Assertions.assertTrue(usersCursor.isConsumed());
213     }
214   }
215 
216   @Test
217   void testCursorMultipleHasNextCall() {
218     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
219       Mapper mapper = sqlSession.getMapper(Mapper.class);
220       Cursor<User> usersCursor = mapper.getAllUsers();
221 
222       Iterator<User> iterator = usersCursor.iterator();
223 
224       Assertions.assertEquals(-1, usersCursor.getCurrentIndex());
225 
226       User user = iterator.next();
227       Assertions.assertEquals("User1", user.getName());
228       Assertions.assertEquals(0, usersCursor.getCurrentIndex());
229 
230       Assertions.assertTrue(iterator.hasNext());
231       Assertions.assertTrue(iterator.hasNext());
232       Assertions.assertTrue(iterator.hasNext());
233       // assert that index has not changed after hasNext() call
234       Assertions.assertEquals(0, usersCursor.getCurrentIndex());
235     }
236   }
237 
238   @Test
239   void testCursorMultipleIteratorCall() {
240     Iterator<User> iterator2 = null;
241     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
242       Mapper mapper = sqlSession.getMapper(Mapper.class);
243       Cursor<User> usersCursor = mapper.getAllUsers();
244 
245       Iterator<User> iterator = usersCursor.iterator();
246       User user = iterator.next();
247       Assertions.assertEquals("User1", user.getName());
248       Assertions.assertEquals(0, usersCursor.getCurrentIndex());
249 
250       iterator2 = usersCursor.iterator();
251       iterator2.hasNext();
252       Assertions.fail("We should have failed since calling iterator several times is not allowed");
253     } catch (IllegalStateException e) {
254       Assertions.assertNull(iterator2, "iterator2 should be null");
255       return;
256     }
257     Assertions.fail("Should have returned earlier");
258   }
259 
260   @Test
261   void testCursorMultipleCloseCall() throws IOException {
262     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
263       Mapper mapper = sqlSession.getMapper(Mapper.class);
264       Cursor<User> usersCursor = mapper.getAllUsers();
265 
266       Assertions.assertFalse(usersCursor.isOpen());
267 
268       Iterator<User> iterator = usersCursor.iterator();
269 
270       // Check if hasNext, fetching is started
271       Assertions.assertTrue(iterator.hasNext());
272       Assertions.assertTrue(usersCursor.isOpen());
273       Assertions.assertFalse(usersCursor.isConsumed());
274 
275       // Consume only the first result
276       User user = iterator.next();
277       Assertions.assertEquals("User1", user.getName());
278 
279       usersCursor.close();
280       // Check multiple close are no-op
281       usersCursor.close();
282 
283       // hasNext now return false, since the cursor is closed
284       Assertions.assertFalse(iterator.hasNext());
285       Assertions.assertFalse(usersCursor.isOpen());
286       Assertions.assertFalse(usersCursor.isConsumed());
287     }
288   }
289 
290   @Test
291   void testCursorUsageAfterClose() throws IOException {
292 
293     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
294       Mapper mapper = sqlSession.getMapper(Mapper.class);
295 
296       Cursor<User> usersCursor = mapper.getAllUsers();
297       try {
298         Iterator<User> iterator = usersCursor.iterator();
299         User user = iterator.next();
300         Assertions.assertEquals("User1", user.getName());
301         Assertions.assertEquals(0, usersCursor.getCurrentIndex());
302 
303         user = iterator.next();
304         Assertions.assertEquals("User2", user.getName());
305         Assertions.assertEquals(1, usersCursor.getCurrentIndex());
306 
307         usersCursor.close();
308 
309         // hasNext now return false, since the cursor is closed
310         Assertions.assertFalse(iterator.hasNext());
311         Assertions.assertFalse(usersCursor.isOpen());
312         Assertions.assertFalse(usersCursor.isConsumed());
313 
314         // trying next() will fail
315         iterator.next();
316 
317         Assertions.fail("We should have failed with NoSuchElementException since Cursor is closed");
318       } catch (NoSuchElementException e) {
319         // We had an exception and current index has not changed
320         Assertions.assertEquals(1, usersCursor.getCurrentIndex());
321         usersCursor.close();
322         return;
323       }
324     }
325 
326     Assertions.fail("Should have returned earlier");
327   }
328 
329   @Test
330   void shouldGetAllUserUsingAnnotationBasedMapper() {
331     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
332       sqlSession.getConfiguration().getMapperRegistry().addMapper(AnnotationMapper.class);
333       AnnotationMapper mapper = sqlSession.getMapper(AnnotationMapper.class);
334       Cursor<User> usersCursor = mapper.getAllUsers();
335 
336       Assertions.assertFalse(usersCursor.isOpen());
337       Assertions.assertFalse(usersCursor.isConsumed());
338       Assertions.assertEquals(-1, usersCursor.getCurrentIndex());
339 
340       List<User> userList = new ArrayList<>();
341       for (User user : usersCursor) {
342         userList.add(user);
343         Assertions.assertEquals(userList.size() - 1, usersCursor.getCurrentIndex());
344       }
345 
346       Assertions.assertFalse(usersCursor.isOpen());
347       Assertions.assertTrue(usersCursor.isConsumed());
348       Assertions.assertEquals(4, usersCursor.getCurrentIndex());
349 
350       Assertions.assertEquals(5, userList.size());
351       User user = userList.get(0);
352       Assertions.assertEquals("User1", user.getName());
353       user = userList.get(1);
354       Assertions.assertEquals("User2", user.getName());
355       user = userList.get(2);
356       Assertions.assertEquals("User3", user.getName());
357       user = userList.get(3);
358       Assertions.assertEquals("User4", user.getName());
359       user = userList.get(4);
360       Assertions.assertEquals("User5", user.getName());
361     }
362   }
363 
364   @Test
365   void shouldThrowIllegalStateExceptionUsingIteratorOnSessionClosed() {
366     Cursor<User> usersCursor;
367     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
368       usersCursor = sqlSession.getMapper(Mapper.class).getAllUsers();
369     }
370     try {
371       usersCursor.iterator();
372       Assertions.fail("Should throws the IllegalStateException when call the iterator method after session is closed.");
373     } catch (IllegalStateException e) {
374       Assertions.assertEquals("A Cursor is already closed.", e.getMessage());
375     }
376 
377     // verify for checking order
378     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
379       usersCursor = sqlSession.getMapper(Mapper.class).getAllUsers();
380       usersCursor.iterator();
381     }
382     try {
383       usersCursor.iterator();
384       Assertions.fail("Should throws the IllegalStateException when call the iterator already.");
385     } catch (IllegalStateException e) {
386       Assertions.assertEquals("Cannot open more than one iterator on a Cursor", e.getMessage());
387     }
388 
389   }
390 
391   @Test
392   void shouldNullItemNotStopIteration() {
393     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
394       Mapper mapper = sqlSession.getMapper(Mapper.class);
395       Cursor<User> cursor = mapper.getNullUsers(new RowBounds());
396       Iterator<User> iterator = cursor.iterator();
397 
398       Assertions.assertFalse(cursor.isOpen());
399 
400       // Cursor is just created, current index is -1
401       Assertions.assertEquals(-1, cursor.getCurrentIndex());
402 
403       // Check if hasNext, fetching is started
404       Assertions.assertTrue(iterator.hasNext());
405       // Re-invoking hasNext() should not fetch the next row
406       Assertions.assertTrue(iterator.hasNext());
407       Assertions.assertTrue(cursor.isOpen());
408       Assertions.assertFalse(cursor.isConsumed());
409 
410       // next() has not been called, index is still -1
411       Assertions.assertEquals(-1, cursor.getCurrentIndex());
412 
413       User user;
414       user = iterator.next();
415       Assertions.assertNull(user);
416       Assertions.assertEquals(0, cursor.getCurrentIndex());
417 
418       Assertions.assertTrue(iterator.hasNext());
419       user = iterator.next();
420       Assertions.assertEquals("Kate", user.getName());
421       Assertions.assertEquals(1, cursor.getCurrentIndex());
422 
423       Assertions.assertTrue(iterator.hasNext());
424       user = iterator.next();
425       Assertions.assertNull(user);
426       Assertions.assertEquals(2, cursor.getCurrentIndex());
427 
428       Assertions.assertTrue(iterator.hasNext());
429       user = iterator.next();
430       Assertions.assertNull(user);
431       Assertions.assertEquals(3, cursor.getCurrentIndex());
432 
433       // Check no more elements
434       Assertions.assertFalse(iterator.hasNext());
435       Assertions.assertFalse(cursor.isOpen());
436       Assertions.assertTrue(cursor.isConsumed());
437     }
438   }
439 
440   @Test
441   void shouldRowBoundsCountNullItem() {
442     try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
443       Mapper mapper = sqlSession.getMapper(Mapper.class);
444       Cursor<User> cursor = mapper.getNullUsers(new RowBounds(1, 2));
445       Iterator<User> iterator = cursor.iterator();
446 
447       Assertions.assertFalse(cursor.isOpen());
448 
449       // Check if hasNext, fetching is started
450       Assertions.assertTrue(iterator.hasNext());
451       // Re-invoking hasNext() should not fetch the next row
452       Assertions.assertTrue(iterator.hasNext());
453       Assertions.assertTrue(cursor.isOpen());
454       Assertions.assertFalse(cursor.isConsumed());
455 
456       User user;
457       user = iterator.next();
458       Assertions.assertEquals("Kate", user.getName());
459       Assertions.assertEquals(1, cursor.getCurrentIndex());
460 
461       Assertions.assertTrue(iterator.hasNext());
462       user = iterator.next();
463       Assertions.assertNull(user);
464       Assertions.assertEquals(2, cursor.getCurrentIndex());
465 
466       // Check no more elements
467       Assertions.assertFalse(iterator.hasNext());
468       Assertions.assertFalse(cursor.isOpen());
469       Assertions.assertTrue(cursor.isConsumed());
470     }
471   }
472 }