1 /*
2  * This file is part of the LibreOffice project.
3  *
4  * This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7  *
8  * This file incorporates work covered by the following license notice:
9  *
10  *   Licensed to the Apache Software Foundation (ASF) under one or more
11  *   contributor license agreements. See the NOTICE file distributed
12  *   with this work for additional information regarding copyright
13  *   ownership. The ASF licenses this file to you under the Apache
14  *   License, Version 2.0 (the "License"); you may not use this file
15  *   except in compliance with the License. You may obtain a copy of
16  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
17  */
18 package complex.dbaccess;
19 
20 import com.sun.star.beans.UnknownPropertyException;
21 import com.sun.star.beans.XPropertySet;
22 import com.sun.star.container.XIndexAccess;
23 import com.sun.star.lang.WrappedTargetException;
24 import com.sun.star.lang.XComponent;
25 import com.sun.star.sdb.CommandType;
26 import com.sun.star.sdb.XParametersSupplier;
27 import com.sun.star.sdb.XResultSetAccess;
28 import com.sun.star.sdb.XRowSetApproveBroadcaster;
29 import com.sun.star.sdbc.SQLException;
30 import com.sun.star.sdbc.XParameters;
31 import com.sun.star.sdbc.XPreparedStatement;
32 import com.sun.star.sdbc.XResultSet;
33 import com.sun.star.sdbc.XResultSetUpdate;
34 import com.sun.star.sdbc.XRow;
35 import com.sun.star.sdbc.XRowSet;
36 import com.sun.star.sdbc.XRowUpdate;
37 import com.sun.star.sdbcx.XColumnsSupplier;
38 import com.sun.star.sdbcx.XDeleteRows;
39 import com.sun.star.sdbcx.XRowLocate;
40 import com.sun.star.uno.UnoRuntime;
41 
42 import connectivity.tools.CRMDatabase;
43 import connectivity.tools.DataSource;
44 import connectivity.tools.HsqlDatabase;
45 import connectivity.tools.sdb.Connection;
46 import java.lang.reflect.Method;
47 import java.util.Random;
48 
49 // ---------- junit imports -----------------
50 import org.junit.After;
51 import org.junit.Test;
52 import static org.junit.Assert.*;
53 
54 
55 public class RowSet extends TestCase
56 {
57 
58     static final int MAX_TABLE_ROWS = 100;
59     static final int MAX_FETCH_ROWS = 10;
60     private static final String NEXT = "next";
61     private static final String TEST21 = "Test21";
62     HsqlDatabase m_database;
63     DataSource m_dataSource;
64     XRowSet m_rowSet;
65     XResultSet m_resultSet;
66     XResultSetUpdate m_resultSetUpdate;
67     XRow m_row;
68     XRowLocate m_rowLocate;
69     XPropertySet m_rowSetProperties;
70     XParametersSupplier m_paramsSupplier;
71 
72     private final Object failedResultSetMovementStressGuard = new Object();
73     private String failedResultSetMovementStressMessages = "";
74 
75     private class ResultSetMovementStress implements Runnable
76     {
77 
78         private final XResultSet m_resultSet;
79         private final XRow m_row;
80         private final int m_id;
81 
ResultSetMovementStress(XResultSet _resultSet, int _id)82         private ResultSetMovementStress(XResultSet _resultSet, int _id) throws java.lang.Exception
83         {
84             m_resultSet = _resultSet;
85             m_row = UnoRuntime.queryInterface( XRow.class, m_resultSet );
86             m_id = _id;
87         }
88 
run()89         public void run()
90         {
91 	    int i=-1;
92             try
93             {
94                 m_resultSet.beforeFirst();
95                 for (i = 0; m_resultSet.next(); ++i)
96                 {
97                     int pos = m_resultSet.getRow();
98                     testPosition(m_resultSet, m_row, i + 1, "clone move(" + m_id + ")");
99                     int pos2 = m_resultSet.getRow();
100                     assertTrue("ResultSetMovementStress wrong position: " + i + " Pos1: " + pos + " Pos2: " + pos2, pos == pos2);
101                 }
102             }
103             catch (Exception e)
104             {
105                 synchronized (failedResultSetMovementStressGuard) {
106                     failedResultSetMovementStressMessages
107                         += "ResultSetMovementStress(" + m_id + ") failed at i="
108                         + i + ": " + e + "\n";
109                 }
110             }
111         }
112     }
113 
createTestCase(boolean _defaultRowSet)114     private void createTestCase(boolean _defaultRowSet) throws Exception
115     {
116         if (m_database == null)
117         {
118             final CRMDatabase database = new CRMDatabase( getMSF(), false );
119             m_database = database.getDatabase();
120             m_dataSource = m_database.getDataSource();
121         }
122 
123         createStructure();
124 
125         if (_defaultRowSet)
126         {
127             createRowSet("TEST1", CommandType.TABLE, true, true);
128         }
129     }
130 
closeAndDeleteDatabase()131     @After public final void closeAndDeleteDatabase() {
132         if (m_database != null) {
133             m_database.closeAndDelete();
134         }
135     }
136 
137     /** creates a com.sun.star.sdb.RowSet to use during the test
138      *  @param command
139      *      the command to use for the RowSet
140      *  @param commandType
141      *      the command type to use for the RowSet
142      *  @param execute
143      *      determines whether the RowSet should be executed
144      */
createRowSet(String command, int commandType, boolean execute)145     private void createRowSet(String command, int commandType, boolean execute) throws com.sun.star.uno.Exception
146     {
147         createRowSet(command, commandType, execute, false);
148     }
149 
150 
151     /** creates a com.sun.star.sdb.RowSet to use during the test
152      *  @param command
153      *      the command to use for the RowSet
154      *  @param commandType
155      *      the command type to use for the RowSet
156      *  @param execute
157      *      determines whether the RowSet should be executed
158      *  @param limitFetchSize
159      *      determines whether the fetch size of the RowSet should be limited to MAX_FETCH_ROWS
160      */
createRowSet(String command, int commandType, boolean execute, boolean limitFetchSize)161     private void createRowSet(String command, int commandType, boolean execute, boolean limitFetchSize) throws com.sun.star.uno.Exception
162     {
163         m_rowSet = UnoRuntime.queryInterface( XRowSet.class, getMSF().createInstance( "com.sun.star.sdb.RowSet" ) );
164         final XPropertySet rowSetProperties = UnoRuntime.queryInterface( XPropertySet.class, m_rowSet );
165         rowSetProperties.setPropertyValue("Command", command);
166         rowSetProperties.setPropertyValue("CommandType", Integer.valueOf(commandType));
167         rowSetProperties.setPropertyValue("ActiveConnection", m_database.defaultConnection().getXConnection());
168         if (limitFetchSize)
169         {
170             rowSetProperties.setPropertyValue("FetchSize", Integer.valueOf(MAX_FETCH_ROWS));
171         }
172 
173         m_resultSet = UnoRuntime.queryInterface( XResultSet.class, m_rowSet );
174         m_resultSetUpdate = UnoRuntime.queryInterface( XResultSetUpdate.class, m_rowSet );
175         m_row = UnoRuntime.queryInterface( XRow.class, m_rowSet );
176         m_rowLocate = UnoRuntime.queryInterface( XRowLocate.class, m_resultSet );
177         m_rowSetProperties = UnoRuntime.queryInterface( XPropertySet.class, m_rowSet );
178         m_paramsSupplier = UnoRuntime.queryInterface( XParametersSupplier.class, m_rowSet );
179 
180         if (execute)
181         {
182             m_rowSet.execute();
183         }
184     }
185 
186 
187     @Test
testRowSet()188     public void testRowSet() throws java.lang.Exception
189     {
190 
191         System.out.println("testing testRowSet");
192         createTestCase(true);
193 
194         // sequential positioning
195         m_resultSet.beforeFirst();
196         testSequentialPositining(m_resultSet, m_row);
197 
198         // absolute positioning
199         testAbsolutePositioning(m_resultSet, m_row);
200 
201         // position during modify
202         testModifyPosition(m_resultSet, m_row);
203 
204         // 3rd test
205         test3(createClone(), m_resultSet);
206         // 4th test
207         test4(m_resultSet);
208 
209         // concurrent (multi threaded) access to the row set and its clones
210         testConcurrentAccess(m_resultSet);
211     }
212 
213 
createClone()214     XResultSet createClone() throws SQLException
215     {
216         final XResultSetAccess rowAcc = UnoRuntime.queryInterface( XResultSetAccess.class, m_rowSet );
217         return rowAcc.createResultSet();
218     }
219 
220 
createStructure()221     void createStructure() throws SQLException
222     {
223         m_database.executeSQL("DROP TABLE \"TEST1\" IF EXISTS");
224         m_database.executeSQL("CREATE TABLE \"TEST1\" (\"ID\" integer not null primary key, \"col2\" varchar(50) )");
225 
226         final Connection connection = m_database.defaultConnection();
227         final XPreparedStatement prep = connection.prepareStatement("INSERT INTO \"TEST1\" values (?,?)");
228         final XParameters para = UnoRuntime.queryInterface( XParameters.class, prep );
229         for (int i = 1; i <= MAX_TABLE_ROWS; ++i)
230         {
231             para.setInt(1, i);
232             para.setString(2, "Test" + i);
233             prep.executeUpdate();
234         }
235 
236         connection.refreshTables();
237     }
238 
239 
testPosition(XResultSet resultSet, XRow row, int expectedValue, String location)240     void testPosition(XResultSet resultSet, XRow row, int expectedValue, String location) throws SQLException
241     {
242         final int val = row.getInt(1);
243         final int pos = resultSet.getRow();
244         assertTrue(location + ": value/position do not match: " + pos + " (pos) != " + val + " (val)", val == pos);
245         assertTrue(location + ": value/position are not as expected: " + val + " (val) != " + expectedValue + " (expected)", val == expectedValue);
246     }
247 
248 
testSequentialPositining(XResultSet _resultSet, XRow _row)249     void testSequentialPositining(XResultSet _resultSet, XRow _row) throws com.sun.star.uno.Exception
250     {
251         // 1st test
252         int i = 1;
253         while (_resultSet.next())
254         {
255             testPosition(_resultSet, _row, i, "testSequentialPositining");
256             ++i;
257         }
258     }
259 
260 
testAbsolutePositioning(XResultSet _resultSet, XRow _row)261     void testAbsolutePositioning(XResultSet _resultSet, XRow _row) throws com.sun.star.uno.Exception
262     {
263         for (int i = 1; i <= MAX_FETCH_ROWS; ++i)
264         {
265             final int calcPos = (MAX_TABLE_ROWS % i) + 1;
266             assertTrue("testAbsolutePositioning failed", _resultSet.absolute(calcPos));
267             testPosition(_resultSet, _row, calcPos, "testAbsolutePositioning");
268         }
269     }
270 
271 
testModifyPosition(XResultSet _resultSet, XRow _row)272     void testModifyPosition(XResultSet _resultSet, XRow _row) throws com.sun.star.uno.Exception
273     {
274         final int testPos = 3;
275         assertTrue("testModifyPosition wants at least " + (testPos+1) + " rows", MAX_FETCH_ROWS >= testPos+1);
276         assertTrue("testModifyPosition failed on moving to row " + testPos, _resultSet.absolute(testPos));
277         UnoRuntime.queryInterface( XRowUpdate.class, _row ).updateString(2, TEST21);
278         testPosition(_resultSet, _row, testPos, "testModifyPosition");
279         UnoRuntime.queryInterface( XResultSetUpdate.class, _resultSet ).cancelRowUpdates();
280     }
281 
282 
test3(XResultSet clone, XResultSet _resultSet)283     void test3(XResultSet clone, XResultSet _resultSet) throws com.sun.star.uno.Exception
284     {
285         final XRow _row = UnoRuntime.queryInterface( XRow.class, _resultSet );
286         final XRow cloneRow = UnoRuntime.queryInterface( XRow.class, clone );
287         for (int i = 1; i <= MAX_FETCH_ROWS; ++i)
288         {
289             final int calcPos = (MAX_TABLE_ROWS % i) + 1;
290             if (clone.absolute(calcPos))
291             {
292                 testPosition(clone, cloneRow, calcPos, "test3");
293                 testAbsolutePositioning(_resultSet, _row);
294                 testAbsolutePositioning(clone, cloneRow);
295             }
296         }
297     }
298 
299 
test4(XResultSet _resultSet)300     void test4(XResultSet _resultSet) throws com.sun.star.uno.Exception
301     {
302         final XRow _row = UnoRuntime.queryInterface( XRow.class, _resultSet );
303         _resultSet.beforeFirst();
304 
305         for (int i = 1; i <= MAX_TABLE_ROWS; ++i)
306         {
307             _resultSet.next();
308             final XResultSet clone = createClone();
309             final XRow cloneRow = UnoRuntime.queryInterface( XRow.class, clone );
310             final int calcPos = MAX_TABLE_ROWS - 1;
311             if (calcPos != 0 && clone.absolute(calcPos))
312             {
313                 testPosition(clone, cloneRow, calcPos, "test4: clone");
314                 testPosition(_resultSet, _row, i, "test4: rowset");
315             }
316         }
317     }
318 
319 
testConcurrentAccess(XResultSet _resultSet)320     void testConcurrentAccess(XResultSet _resultSet) throws Exception
321     {
322         System.out.println("testing Thread");
323 
324         _resultSet.beforeFirst();
325 
326         final int numberOfThreads = 10;
327 
328         final Thread threads[] = new Thread[numberOfThreads];
329         for (int i = 0; i < numberOfThreads; ++i)
330         {
331             threads[i] = new Thread(new ResultSetMovementStress(createClone(), i));
332             System.out.println("starting thread " + (i + 1) + " of " + (numberOfThreads));
333             threads[i].start();
334         }
335 
336         for (int i = 0; i < numberOfThreads; ++i)
337         {
338             threads[i].join();
339         }
340         synchronized (failedResultSetMovementStressGuard) {
341             assertEquals("", failedResultSetMovementStressMessages);
342         }
343     }
344 
345 
346     @Test
testRowSetEvents()347     public void testRowSetEvents() throws java.lang.Exception
348     {
349         System.out.println("testing RowSet Events");
350         createTestCase(true);
351 
352         // first we create our RowSet object
353         final RowSetEventListener pRow = new RowSetEventListener();
354 
355         final XColumnsSupplier colSup = UnoRuntime.queryInterface( XColumnsSupplier.class, m_rowSet );
356         final XPropertySet col = UnoRuntime.queryInterface( XPropertySet.class, colSup.getColumns().getByName( "ID" ) );
357         col.addPropertyChangeListener("Value", pRow);
358         m_rowSetProperties.addPropertyChangeListener("IsModified", pRow);
359         m_rowSetProperties.addPropertyChangeListener("IsNew", pRow);
360         m_rowSetProperties.addPropertyChangeListener("IsRowCountFinal", pRow);
361         m_rowSetProperties.addPropertyChangeListener("RowCount", pRow);
362 
363         final XRowSetApproveBroadcaster xApBroad = UnoRuntime.queryInterface( XRowSetApproveBroadcaster.class, m_resultSet );
364         xApBroad.addRowSetApproveListener(pRow);
365         m_rowSet.addRowSetListener(pRow);
366 
367         // do some movements to check if we got all notifications
368         final Class cResSet = Class.forName("com.sun.star.sdbc.XResultSet");
369         final boolean moves[] = new boolean[9];
370         for (int i = 0; i < moves.length; ++i)
371         {
372             moves[i] = false;
373         }
374         moves[RowSetEventListener.APPROVE_CURSOR_MOVE] = true;
375         moves[RowSetEventListener.COLUMN_VALUE] = true;
376         moves[RowSetEventListener.CURSOR_MOVED] = true;
377         moves[RowSetEventListener.IS_ROW_COUNT_FINAL] = true;
378         moves[RowSetEventListener.ROW_COUNT] = true;
379 
380         testCursorMove(m_resultSet, cResSet.getMethod("afterLast", (Class[]) null), pRow, moves, null);
381 
382         moves[RowSetEventListener.IS_ROW_COUNT_FINAL] = false;
383         moves[RowSetEventListener.ROW_COUNT] = false;
384         testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null);
385         testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null);
386         testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null);
387         testCursorMove(m_resultSet, cResSet.getMethod("last", (Class[]) null), pRow, moves, null);
388         testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null);
389         testCursorMove(m_resultSet, cResSet.getMethod("first", (Class[]) null), pRow, moves, null);
390         testCursorMove(m_resultSet, cResSet.getMethod("previous", (Class[]) null), pRow, moves, null);
391         testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null);
392         moves[RowSetEventListener.IS_MODIFIED] = true;
393         final XRowUpdate updRow = UnoRuntime.queryInterface( XRowUpdate.class, m_resultSet );
394         updRow.updateString(2, TEST21);
395         testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null);
396 
397         moves[RowSetEventListener.IS_MODIFIED] = false;
398         updRow.updateString(2, m_row.getString(2));
399         testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null);
400 
401         moves[RowSetEventListener.IS_MODIFIED] = false;
402         final Class cupd = Class.forName("com.sun.star.sdbc.XResultSetUpdate");
403         final XResultSetUpdate upd = UnoRuntime.queryInterface( XResultSetUpdate.class, m_resultSet );
404         testCursorMove(upd, cupd.getMethod("moveToInsertRow", (Class[]) null), pRow, moves, null);
405 
406         updRow.updateInt(1, MAX_TABLE_ROWS + 2);
407         updRow.updateString(2, "HHHH");
408         moves[RowSetEventListener.APPROVE_CURSOR_MOVE] = false;
409         moves[RowSetEventListener.CURSOR_MOVED] = false;
410         moves[RowSetEventListener.IS_MODIFIED] = true;
411         moves[RowSetEventListener.IS_NEW] = true;
412         moves[RowSetEventListener.ROW_COUNT] = true;
413         moves[RowSetEventListener.APPROVE_ROW_CHANGE] = true;
414         moves[RowSetEventListener.ROW_CHANGED] = true;
415         testCursorMove(upd, cupd.getMethod("insertRow", (Class[]) null), pRow, moves, null);
416 
417         moves[RowSetEventListener.IS_NEW] = false;
418         moves[RowSetEventListener.ROW_COUNT] = false;
419         m_resultSet.first();
420         updRow.updateInt(1, MAX_TABLE_ROWS + 3);
421         updRow.updateString(2, "__");
422         testCursorMove(upd, cupd.getMethod("updateRow", (Class[]) null), pRow, moves, null);
423 
424         moves[RowSetEventListener.IS_NEW] = true;
425         moves[RowSetEventListener.ROW_COUNT] = true;
426         m_resultSet.first();
427         testCursorMove(upd, cupd.getMethod("deleteRow", (Class[]) null), pRow, moves, null);
428 
429         moves[RowSetEventListener.IS_NEW] = false;
430         moves[RowSetEventListener.COLUMN_VALUE] = true;
431         moves[RowSetEventListener.ROW_COUNT] = false;
432         m_resultSet.first();
433         updRow.updateString(2, TEST21);
434         testCursorMove(m_resultSet, cResSet.getMethod("refreshRow", (Class[]) null), pRow, moves, null);
435 
436         m_resultSet.first();
437         updRow.updateString(2, TEST21);
438         testCursorMove(upd, cupd.getMethod("cancelRowUpdates", (Class[]) null), pRow, moves, null);
439 
440         for (int i = 0; i < moves.length; ++i)
441         {
442             moves[i] = false;
443         }
444         moves[RowSetEventListener.APPROVE_CURSOR_MOVE] = true;
445         moves[RowSetEventListener.COLUMN_VALUE] = true;
446         moves[RowSetEventListener.CURSOR_MOVED] = true;
447 
448         final Class cloc = Class.forName("com.sun.star.sdbcx.XRowLocate");
449         m_resultSet.first();
450         final Object bookmark = m_rowLocate.getBookmark();
451         m_resultSet.next();
452         final Object temp[] = new Object[1];
453         temp[0] = bookmark;
454         Class ctemp[] = new Class[1];
455         ctemp[0] = Object.class;
456         testCursorMove(m_rowLocate, cloc.getMethod("moveToBookmark", ctemp), pRow, moves, temp);
457 
458         final Object temp2[] = new Object[2];
459         temp2[0] = bookmark;
460         temp2[1] = Integer.valueOf(1);
461         final Class ctemp2[] = new Class[2];
462         ctemp2[0] = Object.class;
463         ctemp2[1] = int.class;
464         testCursorMove(m_rowLocate, cloc.getMethod("moveRelativeToBookmark", ctemp2), pRow, moves, temp2);
465 
466         for (int i = 0; i < moves.length; ++i)
467         {
468             moves[i] = false;
469         }
470         moves[RowSetEventListener.APPROVE_ROW_CHANGE] = true;
471         moves[RowSetEventListener.ROW_CHANGED] = true;
472         moves[RowSetEventListener.ROW_COUNT] = true;
473         final Class cdelRows = Class.forName("com.sun.star.sdbcx.XDeleteRows");
474         ctemp[0] = Object[].class;
475         final XDeleteRows delRows = UnoRuntime.queryInterface( XDeleteRows.class, m_resultSet );
476         final Object bookmarks[] = new Object[5];
477         m_resultSet.first();
478         for (int i = 0; i < bookmarks.length; ++i)
479         {
480             m_resultSet.next();
481             bookmarks[i] = m_rowLocate.getBookmark();
482         }
483 
484         temp[0] = bookmarks;
485         testCursorMove(delRows, cdelRows.getMethod("deleteRows", ctemp), pRow, moves, temp);
486 
487         // now destroy the RowSet
488         final XComponent xComp = UnoRuntime.queryInterface( XComponent.class, m_resultSet );
489         xComp.dispose();
490     }
491 
492 
testCursorMove(Object res, Method _method, RowSetEventListener _evt, boolean _must[], Object args[])493     private void testCursorMove(Object res, Method _method, RowSetEventListener _evt, boolean _must[], Object args[]) throws java.lang.Exception
494     {
495         _evt.clearCalling();
496         _method.invoke(res, args);
497 
498         System.out.println("testing events for " + _method.getName());
499         final int calling[] = _evt.getCalling();
500         int pos = 1;
501         assertTrue("Callings are not in the correct order for APPROVE_CURSOR_MOVE ",
502                 (!_must[RowSetEventListener.APPROVE_CURSOR_MOVE] || calling[RowSetEventListener.APPROVE_CURSOR_MOVE] == -1) || calling[RowSetEventListener.APPROVE_CURSOR_MOVE] == pos++);
503         assertTrue("Callings are not in the correct order for APPROVE_ROW_CHANGE",
504                 (!_must[RowSetEventListener.APPROVE_ROW_CHANGE] || calling[RowSetEventListener.APPROVE_ROW_CHANGE] == -1) || calling[RowSetEventListener.APPROVE_ROW_CHANGE] == pos++);
505         assertTrue("Callings are not in the correct order for COLUMN_VALUE",
506                 (!_must[RowSetEventListener.COLUMN_VALUE] || calling[RowSetEventListener.COLUMN_VALUE] == -1) || calling[RowSetEventListener.COLUMN_VALUE] == pos++);
507         assertTrue("Callings are not in the correct order for CURSOR_MOVED",
508                 (!_must[RowSetEventListener.CURSOR_MOVED] || calling[RowSetEventListener.CURSOR_MOVED] == -1) || calling[RowSetEventListener.CURSOR_MOVED] == pos++);
509         assertTrue("Callings are not in the correct order for ROW_CHANGED",
510                 (!_must[RowSetEventListener.ROW_CHANGED] || calling[RowSetEventListener.ROW_CHANGED] == -1) || calling[RowSetEventListener.ROW_CHANGED] == pos++);
511         assertTrue("Callings are not in the correct order for IS_MODIFIED",
512                 (!_must[RowSetEventListener.IS_MODIFIED] || calling[RowSetEventListener.IS_MODIFIED] == -1) || calling[RowSetEventListener.IS_MODIFIED] == pos++);
513         assertTrue("Callings are not in the correct order for IS_NEW",
514                 (!_must[RowSetEventListener.IS_NEW] || calling[RowSetEventListener.IS_NEW] == -1) || calling[RowSetEventListener.IS_NEW] == pos++);
515         assertTrue("Callings are not in the correct order for ROW_COUNT",
516                 (!_must[RowSetEventListener.ROW_COUNT] || calling[RowSetEventListener.ROW_COUNT] == -1) || calling[RowSetEventListener.ROW_COUNT] == pos++);
517         assertTrue("Callings are not in the correct order for IS_ROW_COUNT_FINAL",
518                 (!_must[RowSetEventListener.IS_ROW_COUNT_FINAL] || calling[RowSetEventListener.IS_ROW_COUNT_FINAL] == -1) || calling[RowSetEventListener.IS_ROW_COUNT_FINAL] == pos);
519 
520         _evt.clearCalling();
521     }
522 
523 
524     /** returns the current row count of the RowSet
525      */
currentRowCount()526     private int currentRowCount() throws UnknownPropertyException, WrappedTargetException
527     {
528         final Integer rowCount = (Integer) m_rowSetProperties.getPropertyValue("RowCount");
529         return rowCount.intValue();
530     }
531 
532 
533     /** positions the row set at an arbitrary position between 2 and (current row count - 1)
534      */
positionRandom()535     private int positionRandom() throws SQLException, UnknownPropertyException, WrappedTargetException
536     {
537         // note: obviously this should subtract 2 but actually subtract 3
538         // because if we have just deleted the current row then
539         // ORowSetBase::impl_getRowCount() will lie and currentRowCount()
540         // returns 1 more than the actual number of rows and then
541         // positionRandom() followed by deleteRow() deletes *last* row
542         final int position = (new Random()).nextInt(currentRowCount() - 3) + 2;
543         assertTrue("sub task failed: could not position to row no. " + (Integer.valueOf(position)).toString(),
544                 m_resultSet.absolute(position));
545         return m_resultSet.getRow();
546     }
547 
548 
549     /** moves the result set to a random record between 2 and (current row count - 1), and deletes this record
550      *
551      *  After returning from this method, the row set is still positioned at the deleted record
552      *  @return
553      *      the number/position of the record which has been deleted
554      */
deleteRandom()555     private int deleteRandom() throws SQLException, UnknownPropertyException, WrappedTargetException
556     {
557         // check if the current position and the row count in the result set is changed by a deletion (it should not)
558         final int positionBefore = positionRandom();
559         final int rowCountBefore = currentRowCount();
560 
561         m_resultSetUpdate.deleteRow();
562 
563         final int positionAfter = m_resultSet.getRow();
564         final int rowCountAfter = currentRowCount();
565         assertTrue("position changed during |deleteRow| (it should not)", positionAfter == positionBefore);
566         assertTrue("row count changed with a |deleteRow| (it should not)", rowCountBefore == rowCountAfter);
567         assertTrue("RowSet does not report the current row as deleted after |deleteRow|", m_resultSet.rowDeleted());
568 
569         return positionBefore;
570     }
571 
572 
573     @Test
testDeleteBehavior()574     public void testDeleteBehavior() throws Exception
575     {
576         createTestCase(true);
577 
578         // ensure that all records are known
579         m_resultSet.last();
580         final int initialRowCount = currentRowCount();
581 
582         // delete a random row
583         int deletedRow = deleteRandom();
584 
585 
586         // asking for the bookmark of a deleted row should fail
587         boolean caughtException = false;
588         try
589         {
590             m_rowLocate.getBookmark();
591         }
592         catch (SQLException e)
593         {
594             caughtException = true;
595         }
596         assertTrue("asking for the bookmark of a deleted row should throw an exception", caughtException);
597 
598 
599         // isXXX methods should return |false| on a deleted row
600         assertTrue("one of the isFoo failed after |deleteRow|", !m_resultSet.isBeforeFirst() && !m_resultSet.isAfterLast() && !m_resultSet.isFirst() && !m_resultSet.isLast());
601         // note that we can assume that isFirst / isLast also return |false|, since deleteRandom did
602         // not position on the first or last record, but inbetween
603 
604 
605         // check if moving away from this row in either direction yields the expected results
606         assertTrue("|previous| after |deleteRow| failed", m_resultSet.previous());
607         final int positionPrevious = m_resultSet.getRow();
608         assertTrue("position after |previous| after |deleteRow| is not as expected", positionPrevious == deletedRow - 1);
609 
610         deletedRow = deleteRandom();
611         assertTrue("|next| after |deleteRow| failed", m_resultSet.next());
612         final int positionAfter = m_resultSet.getRow();
613         assertTrue("position after |next| after |deleteRow| is not as expected", positionAfter == deletedRow);
614         // since the deleted record "vanishes" as soon as the cursor is moved away from it, the absolute position does
615         // not change with a |next| call here
616 
617 
618         // check if the deleted rows really vanished after moving away from them
619         assertTrue("row count did not change as expected after two deletions", initialRowCount - 2 == currentRowCount());
620 
621 
622         // check if the deleted row vanishes after moving to the insertion row
623         final int rowCountBefore = currentRowCount();
624         final int deletedPos = deleteRandom();
625         m_resultSetUpdate.moveToInsertRow();
626         assertTrue("moving to the insertion row immediately after |deleteRow| does not adjust the row count", rowCountBefore == currentRowCount() + 1);
627 
628         m_resultSetUpdate.moveToCurrentRow();
629         assertTrue("|moveToCurrentRow| after |deleteRow| + |moveToInsertRow| results in unexpected position",
630                 (m_resultSet.getRow() == deletedPos) && !m_resultSet.rowDeleted());
631 
632         // the same, but this time with deleting the first row (which is not covered by deleteRandom)
633         m_resultSet.last();
634         m_resultSetUpdate.deleteRow();
635         m_resultSetUpdate.moveToInsertRow();
636         m_resultSetUpdate.moveToCurrentRow();
637         assertTrue("|last| + |deleteRow| + |moveToInsertRow| + |moveToCurrentRow| results in wrong state", m_resultSet.isAfterLast());
638 
639 
640         // check if deleting a deleted row fails as expected
641         deleteRandom();
642         caughtException = false;
643         try
644         {
645             m_resultSetUpdate.deleteRow();
646         }
647         catch (SQLException e)
648         {
649             caughtException = true;
650         }
651         assertTrue("deleting a deleted row succeeded - it shouldn't", caughtException);
652 
653 
654         // check if deleteRows fails if it contains the bookmark of a previously-deleted row
655         m_resultSet.first();
656         final Object firstBookmark = m_rowLocate.getBookmark();
657         positionRandom();
658         final Object deleteBookmark = m_rowLocate.getBookmark();
659         m_resultSetUpdate.deleteRow();
660         final XDeleteRows multiDelete = UnoRuntime.queryInterface( XDeleteRows.class, m_resultSet );
661         final int[] deleteSuccess = multiDelete.deleteRows(new Object[]
662                 {
663                     firstBookmark, deleteBookmark
664                 });
665         assertTrue("XDeleteRows::deleteRows with the bookmark of an already-deleted row failed",
666                 (deleteSuccess.length == 2) && (deleteSuccess[0] != 0) && (deleteSuccess[1] == 0));
667 
668 
669         // check if refreshing a deleted row fails as expected
670         deleteRandom();
671         caughtException = false;
672         try
673         {
674             m_resultSet.refreshRow();
675         }
676         catch (SQLException e)
677         {
678             caughtException = true;
679         }
680         assertTrue("refreshing a deleted row succeeded - it shouldn't", caughtException);
681 
682 
683         // rowUpdated/rowDeleted
684         deleteRandom();
685         assertTrue("rowDeleted and/or rowUpdated are wrong on a deleted row", !m_resultSet.rowUpdated() && !m_resultSet.rowInserted());
686 
687 
688         // updating values in a deleted row should fail
689         deleteRandom();
690         final XRowUpdate rowUpdated = UnoRuntime.queryInterface( XRowUpdate.class, m_resultSet );
691         caughtException = false;
692         try
693         {
694             rowUpdated.updateString(2, TEST21);
695         }
696         catch (SQLException e)
697         {
698             caughtException = true;
699         }
700         assertTrue("updating values in a deleted row should not succeed", caughtException);
701     }
702 
703 
704     /** checks whether deletions on the main RowSet properly interfere (or don't interfere) with the movement
705      *  on a clone of the RowSet
706      */
707     @Test
testCloneMovesPlusDeletions()708     public void testCloneMovesPlusDeletions() throws Exception
709     {
710         createTestCase(true);
711         // ensure that all records are known
712         m_resultSet.last();
713 
714         final XResultSet clone = createClone();
715         final XRowLocate cloneRowLocate = UnoRuntime.queryInterface( XRowLocate.class, clone );
716 
717         positionRandom();
718 
719 
720         // move the clone to the same record as the RowSet, and delete this record
721         cloneRowLocate.moveToBookmark(m_rowLocate.getBookmark());
722         final int clonePosition = clone.getRow();
723         m_resultSetUpdate.deleteRow();
724 
725         assertTrue("clone doesn't know that its current row has been deleted via the RowSet", clone.rowDeleted());
726         assertTrue("clone's position changed somehow during deletion", clonePosition == clone.getRow());
727 
728 
729         // move the row set away from the deleted record. This should still not touch the state of the clone
730         m_resultSet.previous();
731 
732         assertTrue("clone doesn't know (anymore) that its current row has been deleted via the RowSet", clone.rowDeleted());
733         assertTrue("clone's position changed somehow during deletion and RowSet-movement", clonePosition == clone.getRow());
734 
735 
736         // move the clone away from the deleted record
737         clone.next();
738         assertTrue("clone still assumes that its row is deleted - but we already moved it", !clone.rowDeleted());
739 
740 
741         // check whether deleting the extremes (first / last) work
742         m_resultSet.first();
743         cloneRowLocate.moveToBookmark(m_rowLocate.getBookmark());
744         m_resultSetUpdate.deleteRow();
745         clone.previous();
746         assertTrue("deleting the first record left the clone in a strange state (after |previous|)", clone.isBeforeFirst());
747         clone.next();
748         assertTrue("deleting the first record left the clone in a strange state (after |previous| + |next|)", clone.isFirst());
749 
750         m_resultSet.last();
751         cloneRowLocate.moveToBookmark(m_rowLocate.getBookmark());
752         m_resultSetUpdate.deleteRow();
753         clone.next();
754         assertTrue("deleting the last record left the clone in a strange state (after |next|)", clone.isAfterLast());
755         clone.previous();
756         assertTrue("deleting the first record left the clone in a strange state (after |next| + |previous|)", clone.isLast());
757 
758 
759         // check whether movements of the clone interfere with movements of the RowSet, if the latter is on a deleted row
760         final int positionBefore = positionRandom();
761         m_resultSetUpdate.deleteRow();
762         assertTrue("|deleteRow|, but no |rowDeleted| (this should have been found much earlier!)", m_resultSet.rowDeleted());
763         clone.beforeFirst();
764         while (clone.next()) {}
765         assertTrue("row set forgot that the current row is deleted", m_resultSet.rowDeleted());
766 
767         assertTrue("moving to the next record after |deleteRow| and clone moves failed", m_resultSet.next());
768         assertTrue("wrong position after |deleteRow| and clone movement", !m_resultSet.isAfterLast() && !m_resultSet.isBeforeFirst());
769         assertTrue("wrong absolute position after |deleteRow| and clone movement", m_resultSet.getRow() == positionBefore);
770     }
771 
772 
773     /** checks whether insertions on the main RowSet properly interfere (or don't interfere) with the movement
774      *  on a clone of the RowSet
775      */
776     @Test
testCloneMovesPlusInsertions()777     public void testCloneMovesPlusInsertions() throws Exception
778     {
779         createTestCase(true);
780         // ensure that all records are known
781         m_rowSetProperties.setPropertyValue("FetchSize", Integer.valueOf(10));
782 
783         final XResultSet clone = createClone();
784         final XRow cloneRow = UnoRuntime.queryInterface( XRow.class, clone );
785 
786 
787         // first check the basic scenario without the |moveToInsertRow| |moveToCurrentRow|, to ensure that
788         // really those are broken, if at all
789         m_resultSet.last();
790         clone.first();
791         clone.absolute(11);
792         clone.first();
793 
794         final int rowValue1 = m_row.getInt(1);
795         final int rowPos = m_resultSet.getRow();
796         final int rowValue2 = m_row.getInt(1);
797         assertTrue("repeated query for the same column value delivers different values (" + rowValue1 + " and " + rowValue2 + ") on row: " + rowPos,
798                 rowValue1 == rowValue2);
799 
800         testPosition(clone, cloneRow, 1, "mixed clone/rowset move: clone check");
801         testPosition(m_resultSet, m_row, MAX_TABLE_ROWS, "mixed clone/rowset move: rowset check");
802 
803 
804         // now the complete scenario
805         m_resultSet.last();
806         m_resultSetUpdate.moveToInsertRow();
807         clone.first();
808         clone.absolute(11);
809         clone.first();
810         m_resultSetUpdate.moveToCurrentRow();
811 
812         testPosition(clone, cloneRow, 1, "mixed clone/rowset move/insertion: clone check");
813         testPosition(m_resultSet, m_row, 100, "mixed clone/rowset move/insertion: rowset check");
814     }
815 
816 
testTableParameters()817     private void testTableParameters() throws com.sun.star.uno.Exception
818     {
819         // for a row set simply based on a table, there should be not parameters at all
820         createRowSet("products", CommandType.TABLE, false);
821         verifyParameters(new String[]
822             {
823             }, "testTableParameters");
824     }
825 
826 
testParametersAfterNormalExecute()827     private void testParametersAfterNormalExecute() throws com.sun.star.uno.Exception
828     {
829         createRowSet("SELECT * FROM \"customers\"", CommandType.COMMAND, true);
830         m_rowSetProperties.setPropertyValue("Command", "SELECT * FROM \"customers\" WHERE \"City\" = :city");
831         final XParameters rowsetParams = UnoRuntime.queryInterface( XParameters.class, m_rowSet );
832         rowsetParams.setString(1, "London");
833         m_rowSet.execute();
834     }
835 
836 
verifyParameters(String[] _paramNames, String _context)837     private void verifyParameters(String[] _paramNames, String _context) throws com.sun.star.uno.Exception
838     {
839         final XIndexAccess params = m_paramsSupplier.getParameters();
840         final int expected = _paramNames.length;
841         final int found = params != null ? params.getCount() : 0;
842 
843         assertTrue("wrong number of parameters (expected: " + expected + ", found: " + found + ") in " + _context,
844                 found == expected);
845 
846         if (found == 0)
847         {
848             return;
849         }
850 
851         for (int i = 0; i < expected; ++i)
852         {
853             final XPropertySet parameter = UnoRuntime.queryInterface( XPropertySet.class, params.getByIndex( i ) );
854 
855             final String expectedName = _paramNames[i];
856             final String foundName = (String) parameter.getPropertyValue("Name");
857             assertTrue("wrong parameter name (expected: " + expectedName + ", found: " + foundName + ") in" + _context,
858                     expectedName.equals(foundName));
859         }
860     }
861 
862 
testParametrizedQuery()863     private void testParametrizedQuery() throws com.sun.star.uno.Exception
864     {
865         // for a row set based on a parametrized query, those parameters should be properly
866         // recognized
867         m_dataSource.createQuery("products like", "SELECT * FROM \"products\" WHERE \"Name\" LIKE :product_name");
868         createRowSet("products like", CommandType.QUERY, false);
869         verifyParameters(new String[]
870             {
871                 "product_name"
872             }, "testParametrizedQuery");
873     }
874 
875 
testParametersInteraction()876     private void testParametersInteraction() throws com.sun.star.uno.Exception
877     {
878         createRowSet("products like", CommandType.QUERY, false);
879 
880         // let's fill in a parameter value via XParameters, and see whether it is respected by the parameters container
881         final XParameters rowsetParams = UnoRuntime.queryInterface(XParameters.class, m_rowSet);
882         rowsetParams.setString(1, "Apples");
883 
884         XIndexAccess params = m_paramsSupplier.getParameters();
885         XPropertySet firstParam = UnoRuntime.queryInterface( XPropertySet.class, params.getByIndex( 0 ) );
886         Object firstParamValue = firstParam.getPropertyValue("Value");
887 
888         assertTrue("XParameters and the parameters container do not properly interact",
889                    "Apples".equals(firstParamValue));
890 
891         // let's see whether this also survives an execute of the row set
892         rowsetParams.setString(1, "Oranges");
893         m_rowSet.execute();
894         {
895             // TODO: the following would not be necessary if the parameters container would *survive*
896             // the execution of the row set. It currently doesn't (though the values it represents do).
897             // It would be nice, but not strictly necessary, if it would.
898             params = m_paramsSupplier.getParameters();
899             firstParam = UnoRuntime.queryInterface( XPropertySet.class, params.getByIndex( 0 ) );
900         }
901         firstParamValue = firstParam.getPropertyValue("Value");
902         assertTrue("XParameters and the parameters container do not properly interact, after the row set has been executed",
903                    "Oranges".equals(firstParamValue));
904     }
905 
906 
testParametersInFilter()907     private void testParametersInFilter() throws com.sun.star.uno.Exception
908     {
909         createRowSet("SELECT * FROM \"customers\"", CommandType.COMMAND, false);
910         m_rowSetProperties.setPropertyValue("Filter", "\"City\" = :city");
911 
912         m_rowSetProperties.setPropertyValue("ApplyFilter", Boolean.TRUE);
913         verifyParameters(new String[]
914             {
915                 "city"
916             }, "testParametersInFilter");
917 
918         m_rowSetProperties.setPropertyValue("ApplyFilter", Boolean.FALSE);
919         verifyParameters(new String[]
920             {
921             }, "testParametersInFilter");
922     }
923 
924 
925     /** checks the XParametersSupplier functionality of a RowSet
926      */
927     @Test
testParameters()928     public void testParameters() throws Exception
929     {
930         createTestCase(false);
931         // use an own RowSet instance, not the one which is also used for the other cases
932 
933         testTableParameters();
934         testParametrizedQuery();
935         testParametersInFilter();
936 
937         testParametersAfterNormalExecute();
938 
939         testParametersInteraction();
940     }
941 }
942 
943