summaryrefslogtreecommitdiffstats
path: root/dbaccess/qa/complex
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /dbaccess/qa/complex
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dbaccess/qa/complex')
-rw-r--r--dbaccess/qa/complex/dbaccess/ApplicationController.java159
-rw-r--r--dbaccess/qa/complex/dbaccess/Beamer.java127
-rw-r--r--dbaccess/qa/complex/dbaccess/CRMBasedTestCase.java63
-rw-r--r--dbaccess/qa/complex/dbaccess/CopyTableInterActionHandler.java37
-rw-r--r--dbaccess/qa/complex/dbaccess/CopyTableWizard.java194
-rw-r--r--dbaccess/qa/complex/dbaccess/DataSource.java68
-rw-r--r--dbaccess/qa/complex/dbaccess/DatabaseApplication.java73
-rw-r--r--dbaccess/qa/complex/dbaccess/DatabaseDocument.java996
-rw-r--r--dbaccess/qa/complex/dbaccess/FileHelper.java35
-rw-r--r--dbaccess/qa/complex/dbaccess/Parser.java183
-rw-r--r--dbaccess/qa/complex/dbaccess/PropertyBag.java302
-rw-r--r--dbaccess/qa/complex/dbaccess/Query.java107
-rw-r--r--dbaccess/qa/complex/dbaccess/QueryInQuery.java177
-rw-r--r--dbaccess/qa/complex/dbaccess/RowSet.java942
-rw-r--r--dbaccess/qa/complex/dbaccess/RowSetEventListener.java102
-rw-r--r--dbaccess/qa/complex/dbaccess/SingleSelectQueryComposer.java351
-rw-r--r--dbaccess/qa/complex/dbaccess/TestCase.java232
-rw-r--r--dbaccess/qa/complex/dbaccess/UISettings.java132
-rw-r--r--dbaccess/qa/complex/dbaccess/makefile.mk93
19 files changed, 4373 insertions, 0 deletions
diff --git a/dbaccess/qa/complex/dbaccess/ApplicationController.java b/dbaccess/qa/complex/dbaccess/ApplicationController.java
new file mode 100644
index 0000000000..eafc32dba6
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/ApplicationController.java
@@ -0,0 +1,159 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package complex.dbaccess;
+
+import com.sun.star.beans.PropertyValue;
+import com.sun.star.container.XNameAccess;
+import com.sun.star.frame.FrameSearchFlag;
+import com.sun.star.frame.XComponentLoader;
+import com.sun.star.frame.XModel;
+import com.sun.star.frame.XStorable;
+import com.sun.star.lang.XComponent;
+import com.sun.star.sdb.XOfficeDatabaseDocument;
+import com.sun.star.sdb.application.XDatabaseDocumentUI;
+import com.sun.star.sdbcx.XTablesSupplier;
+import com.sun.star.uno.Exception;
+import com.sun.star.uno.UnoRuntime;
+import connectivity.tools.HsqlColumnDescriptor;
+import connectivity.tools.HsqlDatabase;
+import connectivity.tools.HsqlTableDescriptor;
+import java.io.IOException;
+
+
+// ---------- junit imports -----------------
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+
+/** complex test case for Base's application UI
+ */
+public class ApplicationController extends TestCase
+{
+
+ private HsqlDatabase m_database;
+ private XOfficeDatabaseDocument m_databaseDocument;
+ private XDatabaseDocumentUI m_documentUI;
+
+ public String getTestObjectName()
+ {
+ return getClass().getName();
+ }
+
+
+ private void impl_closeDocument()
+ {
+ if (m_database != null)
+ {
+ m_database.close();
+ m_database = null;
+ m_databaseDocument = null;
+ m_documentUI = null;
+ }
+ }
+
+
+ private void impl_switchToDocument(String _documentURL) throws java.lang.Exception
+ {
+ // close previous database document
+ impl_closeDocument();
+
+ // create/load the new database document
+ m_database = (_documentURL == null)
+ ? new HsqlDatabase(getMSF())
+ : new HsqlDatabase(getMSF(), _documentURL);
+ m_databaseDocument = m_database.getDatabaseDocument();
+
+ // load it into a frame
+ final Object object = getMSF().createInstance("com.sun.star.frame.Desktop");
+ final XComponentLoader xComponentLoader = UnoRuntime.queryInterface(XComponentLoader.class, object);
+ final XComponent loadedComponent = xComponentLoader.loadComponentFromURL(m_database.getDocumentURL(), "_blank", FrameSearchFlag.ALL, new PropertyValue[0]);
+
+ assertTrue("too many document instances!",
+ UnoRuntime.areSame(loadedComponent, m_databaseDocument));
+
+ // get the controller, which provides access to various UI operations
+ final XModel docModel = UnoRuntime.queryInterface(XModel.class,
+ loadedComponent);
+ m_documentUI = UnoRuntime.queryInterface(XDatabaseDocumentUI.class,
+ docModel.getCurrentController());
+ }
+
+
+ @Before
+ @Override
+ public void before() throws java.lang.Exception
+ {
+ super.before();
+ impl_switchToDocument(null);
+ }
+
+
+ @After
+ @Override
+ public void after() throws java.lang.Exception
+ {
+ impl_closeDocument();
+ super.after();
+ }
+
+
+ @Test
+ public void checkSaveAs() throws Exception, IOException, java.lang.Exception
+ {
+ // issue 93737 describes the problem that when you save-as a database document, and do changes to it,
+ // then those changes are saved in the old document, actually
+ final String oldDocumentURL = m_database.getDocumentURL();
+
+ final String newDocumentURL = createTempFileURL();
+
+ // store the doc in a new location
+ final XStorable storeDoc = UnoRuntime.queryInterface( XStorable.class, m_databaseDocument );
+ storeDoc.storeAsURL( newDocumentURL, new PropertyValue[] { } );
+
+ // connect
+ m_documentUI.connect();
+ assertTrue("could not connect to " + m_database.getDocumentURL(), m_documentUI.isConnected());
+
+ // create a table in the database
+ m_database.createTable(new HsqlTableDescriptor("abc", new HsqlColumnDescriptor[]
+ {
+ new HsqlColumnDescriptor("a", "VARCHAR(50)"),
+ new HsqlColumnDescriptor("b", "VARCHAR(50)"),
+ new HsqlColumnDescriptor("c", "VARCHAR(50)")
+ }));
+
+ // load the old document, and verify there is *no* table therein
+ impl_switchToDocument(oldDocumentURL);
+ m_documentUI.connect();
+ assertTrue("could not connect to " + m_database.getDocumentURL(), m_documentUI.isConnected());
+ XTablesSupplier suppTables = UnoRuntime.queryInterface( XTablesSupplier.class, m_documentUI.getActiveConnection() );
+ XNameAccess tables = suppTables.getTables();
+ assertTrue("the table was created in the wrong database", !tables.hasByName("abc"));
+
+ // load the new document, and verify there *is* a table therein
+ impl_switchToDocument(newDocumentURL);
+ m_documentUI.connect();
+ assertTrue("could not connect to " + m_database.getDocumentURL(), m_documentUI.isConnected());
+
+ suppTables = UnoRuntime.queryInterface( XTablesSupplier.class, m_documentUI.getActiveConnection() );
+ tables = suppTables.getTables();
+ assertTrue("the newly created table has not been written", tables.hasByName("abc"));
+ }
+}
diff --git a/dbaccess/qa/complex/dbaccess/Beamer.java b/dbaccess/qa/complex/dbaccess/Beamer.java
new file mode 100644
index 0000000000..3342c70dff
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/Beamer.java
@@ -0,0 +1,127 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package complex.dbaccess;
+
+import com.sun.star.beans.PropertyState;
+import com.sun.star.beans.PropertyValue;
+import com.sun.star.container.XEnumeration;
+import com.sun.star.frame.FrameSearchFlag;
+import com.sun.star.frame.XComponentLoader;
+import com.sun.star.frame.XController;
+import com.sun.star.frame.XDispatch;
+import com.sun.star.frame.XDispatchProvider;
+import com.sun.star.frame.XFrame;
+import com.sun.star.frame.XGlobalEventBroadcaster;
+import com.sun.star.frame.XModel;
+import com.sun.star.frame.theGlobalEventBroadcaster;
+import com.sun.star.lang.XComponent;
+import com.sun.star.sdb.CommandType;
+import com.sun.star.uno.Exception;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.util.URL;
+import com.sun.star.util.XURLTransformer;
+import com.sun.star.view.XSelectionSupplier;
+// ---------- junit imports -----------------
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+
+
+/** complex test case for Base's application UI
+ */
+public class Beamer extends TestCase
+{
+
+ private XModel docModel;
+
+ @Before
+ @Override
+ public void before() throws Exception, java.lang.Exception
+ {
+ // load it into a frame
+ final Object object = getMSF().createInstance("com.sun.star.frame.Desktop");
+ final XComponentLoader xComponentLoader = UnoRuntime.queryInterface(XComponentLoader.class, object);
+ final XComponent loadedComponent = xComponentLoader.loadComponentFromURL("private:factory/swriter", "_blank", 0, new PropertyValue[0]);
+ // get the controller, which provides access to various UI operations
+ docModel = UnoRuntime.queryInterface(XModel.class, loadedComponent);
+ }
+
+
+ @After
+ @Override
+ public void after()
+ {
+ }
+
+
+ @Test
+ public void testBeamer() throws Exception, java.lang.Exception
+ {
+ final XController controller = docModel.getCurrentController();
+ final XFrame frame = controller.getFrame();
+ final XDispatchProvider dispatchP = UnoRuntime.queryInterface(XDispatchProvider.class, frame);
+ URL command = new URL();
+ command.Complete = ".uno:ViewDataSourceBrowser";
+
+ Object instance = getMSF().createInstance("com.sun.star.util.URLTransformer");
+ XURLTransformer atrans = UnoRuntime.queryInterface(XURLTransformer.class, instance);
+ com.sun.star.util.URL[] aURLA = new com.sun.star.util.URL[1];
+ aURLA[0] = command;
+ atrans.parseStrict(aURLA);
+ command = aURLA[0];
+
+ final XDispatch dispatch = dispatchP.queryDispatch(command, "_self", FrameSearchFlag.AUTO);
+ assertNotNull(dispatch);
+ dispatch.dispatch(command, new PropertyValue[0]);
+
+ final PropertyValue[] props = new PropertyValue[]
+ {
+ new PropertyValue("DataSourceName", 0, "Bibliography", PropertyState.DIRECT_VALUE),
+ new PropertyValue("CommandType", 0, Integer.valueOf(CommandType.TABLE), PropertyState.DIRECT_VALUE),
+ new PropertyValue("Command", 0, "biblio", PropertyState.DIRECT_VALUE)
+ };
+
+ final XFrame beamer = frame.findFrame("_beamer", 0);
+ assertNotNull(beamer);
+ final XGlobalEventBroadcaster evtBc = theGlobalEventBroadcaster.get(
+ getComponentContext());
+ XEnumeration enumeration = evtBc.createEnumeration();
+ int count = -1;
+ while (enumeration.hasMoreElements())
+ {
+ enumeration.nextElement();
+ ++count;
+ }
+ final XSelectionSupplier selSup = UnoRuntime.queryInterface(XSelectionSupplier.class, beamer.getController());
+ selSup.select(props);
+ final com.sun.star.util.XCloseable close = UnoRuntime.queryInterface(com.sun.star.util.XCloseable.class, frame);
+ close.close(false);
+
+ enumeration = evtBc.createEnumeration();
+ int count2 = 0;
+ while (enumeration.hasMoreElements())
+ {
+ enumeration.nextElement();
+ ++count2;
+ }
+
+ assertTrue("count1 = " + count + " count2 = " + count2, count == count2);
+ }
+}
diff --git a/dbaccess/qa/complex/dbaccess/CRMBasedTestCase.java b/dbaccess/qa/complex/dbaccess/CRMBasedTestCase.java
new file mode 100644
index 0000000000..a61ffdfa74
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/CRMBasedTestCase.java
@@ -0,0 +1,63 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package complex.dbaccess;
+
+import com.sun.star.sdb.XSingleSelectQueryComposer;
+import connectivity.tools.CRMDatabase;
+
+// ---------- junit imports -----------------
+import org.junit.After;
+import org.junit.Before;
+
+
+public abstract class CRMBasedTestCase extends TestCase
+{
+ protected CRMDatabase m_database;
+
+ protected void createTestCase() throws Exception
+ {
+ m_database = new CRMDatabase( getMSF(), false );
+ }
+
+
+ @Before
+ @Override
+ public void before() throws Exception
+ {
+ createTestCase();
+ }
+
+
+ @After
+ @Override
+ public void after() throws Exception
+ {
+ if ( m_database != null )
+ {
+ m_database.saveAndClose();
+ }
+ }
+
+
+ /** creates a SingleSelectQueryComposer for our connection
+ */
+ protected final XSingleSelectQueryComposer createQueryComposer() throws com.sun.star.uno.Exception
+ {
+ return m_database.getConnection().createSingleSelectQueryComposer();
+ }
+}
diff --git a/dbaccess/qa/complex/dbaccess/CopyTableInterActionHandler.java b/dbaccess/qa/complex/dbaccess/CopyTableInterActionHandler.java
new file mode 100644
index 0000000000..57f4172e3b
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/CopyTableInterActionHandler.java
@@ -0,0 +1,37 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package complex.dbaccess;
+
+import com.sun.star.lib.uno.helper.WeakBase;
+import com.sun.star.task.XInteractionHandler;
+import com.sun.star.task.XInteractionRequest;
+
+import static org.junit.Assert.*;
+
+class CopyTableInterActionHandler extends WeakBase
+ implements XInteractionHandler
+{
+ CopyTableInterActionHandler()
+ {
+ }
+
+ public void handle(XInteractionRequest xRequest)
+ {
+ fail( "interaction handler is not expected to be called" );
+ }
+}
diff --git a/dbaccess/qa/complex/dbaccess/CopyTableWizard.java b/dbaccess/qa/complex/dbaccess/CopyTableWizard.java
new file mode 100644
index 0000000000..f2620f645e
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/CopyTableWizard.java
@@ -0,0 +1,194 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package complex.dbaccess;
+
+import com.sun.star.accessibility.XAccessible;
+import com.sun.star.accessibility.XAccessibleContext;
+import com.sun.star.awt.XExtendedToolkit;
+import com.sun.star.awt.XWindow;
+import com.sun.star.beans.Optional;
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.container.XNameAccess;
+import com.sun.star.sdb.CommandType;
+import com.sun.star.sdb.application.XCopyTableWizard;
+import com.sun.star.sdb.DataAccessDescriptorFactory;
+import com.sun.star.sdbc.XConnection;
+import com.sun.star.sdbcx.XTablesSupplier;
+import com.sun.star.task.XInteractionHandler;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.uno.XComponentContext;
+import connectivity.tools.DbaseDatabase;
+import java.io.IOException;
+import util.UITools;
+
+// ---------- junit imports -----------------
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+
+/** complex test case for Base's application UI
+ */
+public class CopyTableWizard extends CRMBasedTestCase
+{
+
+ private DatabaseApplication source;
+ private DbaseDatabase destinationDB = null;
+ private DatabaseApplication dest;
+
+ @After
+ @Override
+ public void after() throws Exception
+ {
+ dest.store();
+ if ( destinationDB != null )
+ destinationDB.close();
+ destinationDB = null;
+ super.after();
+ }
+
+ @Before
+ @Override
+ public void before() throws Exception
+ {
+ createTestCase();
+ source = new DatabaseApplication(m_database.getDatabase());
+ destinationDB = new DbaseDatabase( getMSF() );
+ dest = new DatabaseApplication( destinationDB );
+ }
+
+
+ private static class CopyThread implements Runnable
+ {
+
+ private final XCopyTableWizard copyWizard;
+
+ private CopyThread(final XCopyTableWizard copyWizard)
+ {
+ this.copyWizard = copyWizard;
+ }
+
+ public void run()
+ {
+ copyWizard.execute();
+ }
+ }
+
+ private XWindow getActiveWindow()
+ {
+ Object toolKit = null;
+ try
+ {
+ toolKit = getMSF().createInstance("com.sun.star.awt.Toolkit");
+ }
+ catch (com.sun.star.uno.Exception e)
+ {
+ return null;
+ }
+
+ XExtendedToolkit tk = UnoRuntime.queryInterface( XExtendedToolkit.class, toolKit );
+ Object atw = tk.getActiveTopWindow();
+ return UnoRuntime.queryInterface( XWindow.class, atw );
+ }
+
+ @Test
+ public void copyTable() throws Exception, IOException, java.lang.Exception
+ {
+ copyTable(source,source);
+ }
+
+ @Test
+ public void copyTableDbase() throws Exception, IOException, java.lang.Exception
+ {
+ copyTable(source,dest);
+ }
+ private void copyTable(final DatabaseApplication sourceDb,final DatabaseApplication destDb) throws Exception, IOException, java.lang.Exception
+ {
+ final XConnection destConnection = destDb.getDocumentUI().getActiveConnection();
+
+ final XConnection sourceConnection = sourceDb.getDocumentUI().getActiveConnection();
+ final XTablesSupplier suppTables = UnoRuntime.queryInterface(XTablesSupplier.class, sourceConnection);
+ final XNameAccess tables = suppTables.getTables();
+
+ final String[] names = tables.getElementNames();
+ for (int i = 0; i < names.length; i++)
+ {
+ copyTable(names[i], sourceConnection, destConnection);
+ }
+ }
+
+ private void copyTable(final String tableName, final XConnection sourceConnection, final XConnection destConnection) throws Exception, java.lang.Exception
+ {
+
+ final XInteractionHandler interAction = new CopyTableInterActionHandler();
+ final XComponentContext context = getComponentContext();
+ final XPropertySet sourceDescriptor = DataAccessDescriptorFactory.get(context).createDataAccessDescriptor();
+ sourceDescriptor.setPropertyValue("CommandType", CommandType.TABLE);
+ sourceDescriptor.setPropertyValue("Command", tableName);
+ sourceDescriptor.setPropertyValue("ActiveConnection", sourceConnection);
+
+ final XPropertySet destDescriptor = DataAccessDescriptorFactory.get(context).createDataAccessDescriptor();
+ destDescriptor.setPropertyValue("ActiveConnection", destConnection);
+
+ final XCopyTableWizard copyWizard = com.sun.star.sdb.application.CopyTableWizard.createWithInteractionHandler(
+ context, sourceDescriptor, destDescriptor, interAction);
+ copyWizard.setOperation((short) 0); // com.sun.star.sdb.application.CopyDefinitionAndData
+ Optional<String> auto = new Optional<String>();
+
+ auto.IsPresent = destConnection.getMetaData().supportsCoreSQLGrammar();
+ if (auto.IsPresent)
+ {
+ auto.Value = "ID_test";
+ }
+ copyWizard.setCreatePrimaryKey(auto);
+ Thread thread = new Thread(new CopyThread(copyWizard));
+ thread.start();
+ util.utils.shortWait();
+
+ try
+ {
+ final XWindow dialog = getActiveWindow();
+ final UITools uiTools = new UITools(dialog);
+ final XAccessible root = uiTools.getRoot();
+ final XAccessibleContext accContext = root.getAccessibleContext();
+ final long count = accContext.getAccessibleChildCount();
+ String buttonName = "Create";
+ final XAccessibleContext childContext = accContext.getAccessibleChild(count - 3).getAccessibleContext();
+ final String name = childContext.getAccessibleName();
+ if (name != null && !"".equals(name))
+ {
+ buttonName = name;
+ }
+ try
+ {
+ uiTools.clickButton(buttonName);
+ }
+ catch (java.lang.Exception exception)
+ {
+ exception.printStackTrace( System.err );
+ }
+ }
+ catch (com.sun.star.lang.IndexOutOfBoundsException indexOutOfBoundsException)
+ {
+ }
+ util.utils.shortWait();
+
+ thread.join();
+ }
+
+}
diff --git a/dbaccess/qa/complex/dbaccess/DataSource.java b/dbaccess/qa/complex/dbaccess/DataSource.java
new file mode 100644
index 0000000000..0413a6e07b
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/DataSource.java
@@ -0,0 +1,68 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package complex.dbaccess;
+
+import com.sun.star.container.XNameAccess;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.uno.XNamingService;
+import connectivity.tools.CRMDatabase;
+import connectivity.tools.HsqlDatabase;
+
+// ---------- junit imports -----------------
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+
+
+public class DataSource extends TestCase
+{
+
+ HsqlDatabase m_database;
+ connectivity.tools.DataSource m_dataSource;
+
+
+ private void createTestCase() throws Exception
+ {
+ if (m_database == null)
+ {
+ final CRMDatabase database = new CRMDatabase( getMSF(), false );
+ m_database = database.getDatabase();
+ m_dataSource = m_database.getDataSource();
+ }
+ }
+
+
+ @Test
+ public void testRegistrationName() throws Exception
+ {
+ createTestCase();
+ // 1. check the existing "Bibliography" data source whether it has the proper name
+ String dataSourceName = "Bibliography";
+ final connectivity.tools.DataSource bibliography = new connectivity.tools.DataSource(getMSF(), dataSourceName);
+ assertEquals("pre-registered database has a wrong name!", dataSourceName, bibliography.getName());
+ // 2. register a newly created data source, and verify it has the proper name
+ dataSourceName = "someDataSource";
+ final XNamingService dataSourceRegistrations = UnoRuntime.queryInterface(
+ XNamingService.class, getMSF().createInstance( "com.sun.star.sdb.DatabaseContext" ) );
+ final XNameAccess existenceCheck = UnoRuntime.queryInterface( XNameAccess.class, dataSourceRegistrations );
+ if ( existenceCheck.hasByName( "someDataSource" ) )
+ dataSourceRegistrations.revokeObject( "someDataSource" );
+ dataSourceRegistrations.registerObject("someDataSource", m_dataSource.getXDataSource());
+ assertEquals("registration name of a newly registered data source is wrong", dataSourceName, m_dataSource.getName());
+ }
+}
diff --git a/dbaccess/qa/complex/dbaccess/DatabaseApplication.java b/dbaccess/qa/complex/dbaccess/DatabaseApplication.java
new file mode 100644
index 0000000000..dad7644f8b
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/DatabaseApplication.java
@@ -0,0 +1,73 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package complex.dbaccess;
+
+import com.sun.star.beans.PropertyValue;
+import com.sun.star.frame.FrameSearchFlag;
+import com.sun.star.frame.XComponentLoader;
+import com.sun.star.frame.XModel;
+import com.sun.star.frame.XStorable;
+import com.sun.star.lang.XComponent;
+import com.sun.star.sdb.XOfficeDatabaseDocument;
+import com.sun.star.sdb.application.XDatabaseDocumentUI;
+import com.sun.star.uno.Exception;
+import com.sun.star.uno.UnoRuntime;
+import connectivity.tools.DatabaseAccess;
+
+public class DatabaseApplication
+{
+
+ private final XOfficeDatabaseDocument databaseDocument;
+ private final XDatabaseDocumentUI documentUI;
+
+ public DatabaseApplication(final DatabaseAccess _db) throws Exception
+ {
+ databaseDocument = _db.getDatabaseDocument();
+
+ // load it into a frame
+ final Object object = _db.getORB().createInstance("com.sun.star.frame.Desktop");
+ final XComponentLoader xComponentLoader = UnoRuntime.queryInterface(XComponentLoader.class, object);
+ final XComponent loadedComponent = xComponentLoader.loadComponentFromURL(_db.getDocumentURL(), "_blank", FrameSearchFlag.ALL, new PropertyValue[0]);
+
+ // get the controller, which provides access to various UI operations
+ final XModel docModel = UnoRuntime.queryInterface(XModel.class, loadedComponent);
+ documentUI = UnoRuntime.queryInterface(XDatabaseDocumentUI.class, docModel.getCurrentController());
+ documentUI.connect();
+ }
+
+ public XDatabaseDocumentUI getDocumentUI()
+ {
+ return documentUI;
+ }
+
+ public void store()
+ {
+ // store the doc in a new location
+ try
+ {
+ final XStorable storeDoc = UnoRuntime.queryInterface(XStorable.class, databaseDocument);
+ if (storeDoc != null)
+ {
+ storeDoc.store();
+ }
+ }
+ catch (com.sun.star.io.IOException iOException)
+ {
+ }
+ }
+}
diff --git a/dbaccess/qa/complex/dbaccess/DatabaseDocument.java b/dbaccess/qa/complex/dbaccess/DatabaseDocument.java
new file mode 100644
index 0000000000..3087ad6374
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/DatabaseDocument.java
@@ -0,0 +1,996 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package complex.dbaccess;
+
+import com.sun.star.configuration.theDefaultProvider;
+import com.sun.star.lang.NotInitializedException;
+import com.sun.star.frame.DoubleInitializationException;
+import com.sun.star.awt.XTopWindow;
+import com.sun.star.beans.PropertyState;
+import com.sun.star.document.DocumentEvent;
+import com.sun.star.lang.XEventListener;
+import com.sun.star.lang.XMultiServiceFactory;
+import com.sun.star.script.XStorageBasedLibraryContainer;
+import com.sun.star.task.XInteractionRequest;
+
+import com.sun.star.uno.Type;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.frame.XStorable;
+import com.sun.star.beans.NamedValue;
+import com.sun.star.beans.PropertyValue;
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.container.XNameContainer;
+import com.sun.star.container.XSet;
+import com.sun.star.document.XDocumentEventListener;
+import com.sun.star.document.XEmbeddedScripts;
+import com.sun.star.document.XEventsSupplier;
+import com.sun.star.lang.XComponent;
+import com.sun.star.frame.XComponentLoader;
+import com.sun.star.frame.XDispatch;
+import com.sun.star.frame.XDispatchProvider;
+import com.sun.star.frame.XFrame;
+import com.sun.star.frame.XGlobalEventBroadcaster;
+import com.sun.star.frame.XLoadable;
+import com.sun.star.frame.XModel;
+import com.sun.star.frame.XModel2;
+import com.sun.star.frame.XTitle;
+import com.sun.star.frame.theGlobalEventBroadcaster;
+import com.sun.star.lang.EventObject;
+import com.sun.star.lang.XServiceInfo;
+import com.sun.star.lang.XSingleComponentFactory;
+import com.sun.star.lang.XTypeProvider;
+import com.sun.star.script.provider.XScriptProviderSupplier;
+import com.sun.star.sdb.XDocumentDataSource;
+
+import com.sun.star.sdb.XFormDocumentsSupplier;
+import com.sun.star.sdb.XOfficeDatabaseDocument;
+import com.sun.star.sdb.XReportDocumentsSupplier;
+import com.sun.star.task.DocumentMacroConfirmationRequest;
+import com.sun.star.task.XInteractionApprove;
+import com.sun.star.task.XInteractionContinuation;
+import com.sun.star.task.XInteractionHandler;
+import com.sun.star.uno.XComponentContext;
+import com.sun.star.util.CloseVetoException;
+import com.sun.star.util.URL;
+import com.sun.star.util.XChangesBatch;
+import com.sun.star.util.XCloseable;
+import com.sun.star.util.XModifiable;
+import com.sun.star.util.XURLTransformer;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+// ---------- junit imports -----------------
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+
+public class DatabaseDocument extends TestCase implements com.sun.star.document.XDocumentEventListener
+{
+
+ private static final String _BLANK = "_blank";
+ private XComponent m_callbackFactory = null;
+ private final ArrayList<String> m_documentEvents = new ArrayList<String>();
+ private final ArrayList<String> m_globalEvents = new ArrayList<String>();
+ // for those states, see testDocumentEvents
+ private static short STATE_NOT_STARTED = 0;
+ private static short STATE_LOADING_DOC = 1;
+ private static short STATE_MACRO_EXEC_APPROVED = 2;
+ private static short STATE_ON_LOAD_RECEIVED = 3;
+ private short m_loadDocState = STATE_NOT_STARTED;
+
+
+ /** a helper class which can be used by the Basic scripts in our test documents
+ * to notify us of events in this document
+ */
+ private class CallbackComponent implements XDocumentEventListener, XTypeProvider
+ {
+
+ public void documentEventOccured(DocumentEvent _event)
+ {
+ onDocumentEvent(_event);
+ }
+
+ public void disposing(com.sun.star.lang.EventObject _Event)
+ {
+ // not interested in
+ }
+
+ public Type[] getTypes()
+ {
+ final Class interfaces[] = getClass().getInterfaces();
+ Type types[] = new Type[interfaces.length];
+ for (int i = 0; i < interfaces.length; ++i)
+ {
+ types[i] = new Type(interfaces[i]);
+ }
+ return types;
+ }
+
+ public byte[] getImplementationId()
+ {
+ return new byte[0];
+ }
+ }
+
+
+ private static String getCallbackComponentServiceName()
+ {
+ return "org.openoffice.complex.dbaccess.EventCallback";
+ }
+
+
+ /** a factory for a CallbackComponent
+ */
+ private class CallbackComponentFactory implements XSingleComponentFactory, XServiceInfo, XComponent
+ {
+
+ private final ArrayList<XEventListener> m_eventListeners = new ArrayList<XEventListener>();
+
+ public Object createInstanceWithContext(XComponentContext _context) throws com.sun.star.uno.Exception
+ {
+ return new CallbackComponent();
+ }
+
+ public Object createInstanceWithArgumentsAndContext(Object[] arg0, XComponentContext _context) throws com.sun.star.uno.Exception
+ {
+ return createInstanceWithContext(_context);
+ }
+
+ public String getImplementationName()
+ {
+ return "org.openoffice.complex.dbaccess.CallbackComponent";
+ }
+
+ public boolean supportsService(String _service)
+ {
+ return _service.equals(getCallbackComponentServiceName());
+ }
+
+ public String[] getSupportedServiceNames()
+ {
+ return new String[]
+ {
+ getCallbackComponentServiceName()
+ };
+ }
+
+ @SuppressWarnings("unchecked")
+ public void dispose()
+ {
+ final EventObject event = new EventObject(this);
+
+ final ArrayList<XEventListener> eventListenersCopy = (ArrayList<XEventListener>)m_eventListeners.clone();
+ final Iterator<XEventListener> iter = eventListenersCopy.iterator();
+ while (iter.hasNext())
+ {
+ iter.next().disposing(event);
+ }
+ }
+
+ public void addEventListener(XEventListener _listener)
+ {
+ if (_listener != null)
+ {
+ m_eventListeners.add(_listener);
+ }
+ }
+
+ public void removeEventListener(XEventListener _listener)
+ {
+ m_eventListeners.remove(_listener);
+ }
+ }
+
+
+ private class MacroExecutionApprove implements XInteractionHandler
+ {
+
+ private XInteractionHandler m_defaultHandler = null;
+
+ MacroExecutionApprove(XMultiServiceFactory _factory)
+ {
+ try
+ {
+ m_defaultHandler = UnoRuntime.queryInterface(XInteractionHandler.class, _factory.createInstance("com.sun.star.task.InteractionHandler"));
+ }
+ catch (Exception ex)
+ {
+ Logger.getLogger(DatabaseDocument.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+
+ public void handle(XInteractionRequest _request)
+ {
+ final Object request = _request.getRequest();
+ if (!(request instanceof DocumentMacroConfirmationRequest) && (m_defaultHandler != null))
+ {
+ m_defaultHandler.handle(_request);
+ return;
+ }
+
+ assertEquals("interaction handler called in wrong state", STATE_LOADING_DOC, m_loadDocState);
+
+ // auto-approve
+ final XInteractionContinuation continuations[] = _request.getContinuations();
+ for (int i = 0; i < continuations.length; ++i)
+ {
+ final XInteractionApprove approve = UnoRuntime.queryInterface(XInteractionApprove.class, continuations[i]);
+ if (approve != null)
+ {
+ approve.select();
+ m_loadDocState = STATE_MACRO_EXEC_APPROVED;
+ break;
+ }
+ }
+ }
+ }
+
+
+
+ @Override
+ @Before
+ public void before() throws java.lang.Exception
+ {
+ super.before();
+
+ try
+ {
+ // at our service factory, insert a new factory for our CallbackComponent
+ // this will allow the Basic code in our test documents to call back into this test case
+ // here, by just instantiating this service
+ final XSet globalFactory = UnoRuntime.queryInterface(XSet.class, getMSF());
+ m_callbackFactory = new CallbackComponentFactory();
+ globalFactory.insert(m_callbackFactory);
+
+ // register ourself as listener at the global event broadcaster
+ final XGlobalEventBroadcaster broadcaster
+ = theGlobalEventBroadcaster.get(getComponentContext());
+ broadcaster.addDocumentEventListener(this);
+ }
+ catch (Exception e)
+ {
+ System.out.println("could not create the test case, error message:\n" + e.getMessage());
+ e.printStackTrace(System.err);
+ fail("failed to create the test case");
+ }
+ }
+
+
+ @Override
+ @After
+ public void after() throws java.lang.Exception
+ {
+ try
+ {
+ // dispose our callback factory. This will automatically remove it from our service
+ // factory
+ m_callbackFactory.dispose();
+
+ // revoke ourself as listener at the global event broadcaster
+ final XGlobalEventBroadcaster broadcaster
+ = theGlobalEventBroadcaster.get(getComponentContext());
+ broadcaster.removeDocumentEventListener(this);
+ }
+ catch (Exception e)
+ {
+ System.out.println("could not create the test case, error message:\n" + e.getMessage());
+ e.printStackTrace(System.err);
+ fail("failed to close the test case");
+ }
+
+ super.after();
+ }
+
+
+ private static class UnoMethodDescriptor
+ {
+
+ public Class unoInterfaceClass = null;
+ public String methodName = null;
+
+ UnoMethodDescriptor(Class _class, String _method)
+ {
+ unoInterfaceClass = _class;
+ methodName = _method;
+ }
+ }
+
+
+ private void impl_checkDocumentInitState(Object _document, boolean _isInitialized)
+ {
+ // things you cannot do with an uninitialized document:
+ final UnoMethodDescriptor[] unsupportedMethods = new UnoMethodDescriptor[]
+ {
+ new UnoMethodDescriptor(XStorable.class, "store"),
+ new UnoMethodDescriptor(XFormDocumentsSupplier.class, "getFormDocuments"),
+ new UnoMethodDescriptor(XReportDocumentsSupplier.class, "getReportDocuments"),
+ new UnoMethodDescriptor(XScriptProviderSupplier.class, "getScriptProvider"),
+ new UnoMethodDescriptor(XEventsSupplier.class, "getEvents"),
+ new UnoMethodDescriptor(XTitle.class, "getTitle"),
+ new UnoMethodDescriptor(XModel2.class, "getControllers")
+ // (there's much more than this, but we cannot list all methods here, can we ...)
+ };
+
+ for (int i = 0; i < unsupportedMethods.length; ++i)
+ {
+ assureException( _document, unsupportedMethods[i].unoInterfaceClass,
+ unsupportedMethods[i].methodName, new Object[]{}, _isInitialized ? null : NotInitializedException.class );
+ }
+ }
+
+
+ private XModel impl_createDocument() throws Exception
+ {
+ final XModel databaseDoc = UnoRuntime.queryInterface(XModel.class, getMSF().createInstance("com.sun.star.sdb.OfficeDatabaseDocument"));
+
+ // should not be initialized here - we did neither initNew nor load nor storeAsURL it
+ impl_checkDocumentInitState(databaseDoc, false);
+
+ return databaseDoc;
+ }
+
+
+ private void impl_closeDocument(XModel _databaseDoc) throws CloseVetoException, Exception
+ {
+ final XCloseable closeDoc = UnoRuntime.queryInterface(XCloseable.class, _databaseDoc);
+ closeDoc.close(true);
+ }
+
+
+ private XModel impl_createEmptyEmbeddedHSQLDocument() throws Exception, IOException
+ {
+ final XModel databaseDoc = UnoRuntime.queryInterface(XModel.class, getMSF().createInstance("com.sun.star.sdb.OfficeDatabaseDocument"));
+ final XStorable storeDoc = UnoRuntime.queryInterface(XStorable.class, databaseDoc);
+
+ // verify the document rejects API calls which require it to be initialized
+ impl_checkDocumentInitState(databaseDoc, false);
+
+ // though the document is not initialized, you can ask for the location, the URL, and the args
+ final String location = storeDoc.getLocation();
+ final String url = databaseDoc.getURL();
+ final PropertyValue[] args = databaseDoc.getArgs();
+ // they should be all empty at this time
+ assertEquals("location is expected to be empty here", "", location);
+ assertEquals("URL is expected to be empty here", "", url);
+ assertEquals("Args are expected to be empty here", 0, args.length);
+
+ // and, you should be able to set properties at the data source
+ final XOfficeDatabaseDocument dataSourceAccess = UnoRuntime.queryInterface(XOfficeDatabaseDocument.class, databaseDoc);
+ final XPropertySet dsProperties = UnoRuntime.queryInterface(XPropertySet.class, dataSourceAccess.getDataSource());
+ dsProperties.setPropertyValue("URL", "sdbc:embedded:hsqldb");
+
+ final String documentURL = createTempFileURL();
+ storeDoc.storeAsURL(documentURL, new PropertyValue[0]);
+
+ // now that the document is stored, ...
+ // ... its URL should be correct
+ assertEquals("wrong URL after storing the document", documentURL, databaseDoc.getURL());
+ // ... it should be initialized
+ impl_checkDocumentInitState(databaseDoc, true);
+
+ return databaseDoc;
+ }
+
+
+ @Test
+ public void testLoadable() throws Exception, IOException
+ {
+ XModel databaseDoc = impl_createEmptyEmbeddedHSQLDocument();
+ String documentURL = databaseDoc.getURL();
+
+ // there's three methods how you can initialize a database document:
+
+
+ // 1. XStorable::storeAsURL
+ // (this is for compatibility reasons, to not break existing code)
+ // this test is already made in impl_createEmptyEmbeddedHSQLDocument
+
+
+ // 2. XLoadable::load
+ databaseDoc = UnoRuntime.queryInterface(XModel.class, getMSF().createInstance("com.sun.star.sdb.OfficeDatabaseDocument"));
+ documentURL = copyToTempFile(documentURL);
+ // load the doc, and verify it's initialized then, and has the proper URL
+ XLoadable loadDoc = UnoRuntime.queryInterface(XLoadable.class, databaseDoc);
+ loadDoc.load(new PropertyValue[]
+ {
+ new PropertyValue("URL", 0, documentURL, PropertyState.DIRECT_VALUE)
+ });
+ databaseDoc.attachResource(documentURL, new PropertyValue[0]);
+
+ assertEquals("wrong URL after loading the document", documentURL, databaseDoc.getURL());
+ impl_checkDocumentInitState(databaseDoc, true);
+
+ // and while we are here ... initializing the same document again should not be possible
+ assureException( databaseDoc, XLoadable.class, "initNew", new Object[0],
+ DoubleInitializationException.class );
+ assureException( databaseDoc, XLoadable.class, "load", new Object[] { new PropertyValue[0] },
+ DoubleInitializationException.class );
+
+
+ // 3. XLoadable::initNew
+ impl_closeDocument(databaseDoc);
+ databaseDoc = impl_createDocument();
+ loadDoc = UnoRuntime.queryInterface(XLoadable.class, databaseDoc);
+ loadDoc.initNew();
+ assertEquals("wrong URL after initializing the document", "", databaseDoc.getURL());
+ impl_checkDocumentInitState(databaseDoc, true);
+
+ // same as above - initializing the document a second time must fail
+ assureException( databaseDoc, XLoadable.class, "initNew", new Object[0],
+ DoubleInitializationException.class );
+ assureException( databaseDoc, XLoadable.class, "load", new Object[] { new PropertyValue[0] },
+ DoubleInitializationException.class );
+ }
+
+
+ private PropertyValue[] impl_getMarkerLoadArgs()
+ {
+ return new PropertyValue[]
+ {
+ new PropertyValue( "PickListEntry", 0, false, PropertyState.DIRECT_VALUE ),
+ new PropertyValue( "TestCase_Marker", 0, "Yes", PropertyState.DIRECT_VALUE )
+ };
+ }
+
+
+ private boolean impl_hasMarker( final PropertyValue[] _args )
+ {
+ for ( int i=0; i<_args.length; ++i )
+ {
+ if ( _args[i].Name.equals( "TestCase_Marker" ) && _args[i].Value.equals( "Yes" ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ private PropertyValue[] impl_getDefaultLoadArgs()
+ {
+ return new PropertyValue[]
+ {
+ new PropertyValue("PickListEntry", 0, false, PropertyState.DIRECT_VALUE)
+ };
+ }
+
+
+ private PropertyValue[] impl_getMacroExecLoadArgs()
+ {
+ return new PropertyValue[]
+ {
+ new PropertyValue("PickListEntry", 0, false, PropertyState.DIRECT_VALUE),
+ new PropertyValue("MacroExecutionMode", 0, com.sun.star.document.MacroExecMode.USE_CONFIG, PropertyState.DIRECT_VALUE),
+ new PropertyValue("InteractionHandler", 0, new MacroExecutionApprove(getMSF()), PropertyState.DIRECT_VALUE)
+ };
+ }
+
+
+ private int impl_setMacroSecurityLevel(int _level) throws Exception
+ {
+ final XMultiServiceFactory configProvider = theDefaultProvider.get(
+ getComponentContext());
+
+ final NamedValue[] args = new NamedValue[]
+ {
+ new NamedValue("nodepath", "/org.openoffice.Office.Common/Security/Scripting")
+ };
+
+ final XPropertySet securitySettings = UnoRuntime.queryInterface(XPropertySet.class, configProvider.createInstanceWithArguments("com.sun.star.configuration.ConfigurationUpdateAccess", args));
+ final int oldValue = ((Integer) securitySettings.getPropertyValue("MacroSecurityLevel")).intValue();
+ securitySettings.setPropertyValue("MacroSecurityLevel", Integer.valueOf(_level));
+
+ final XChangesBatch committer = UnoRuntime.queryInterface(XChangesBatch.class, securitySettings);
+ committer.commitChanges();
+
+ return oldValue;
+ }
+
+
+ private XModel impl_loadDocument( final String _documentURL, final PropertyValue[] _loadArgs ) throws Exception
+ {
+ final XComponentLoader loader = UnoRuntime.queryInterface(XComponentLoader.class, getMSF().createInstance("com.sun.star.frame.Desktop"));
+ return UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(_documentURL, _BLANK, 0, _loadArgs));
+ }
+
+
+ private void impl_storeDocument( final XModel _document ) throws Exception
+ {
+ // store the document
+ FileHelper.getOOoCompatibleFileURL( _document.getURL() );
+ final XStorable storeDoc = UnoRuntime.queryInterface(XStorable.class, _document);
+ storeDoc.store();
+
+ }
+
+
+ private XModel impl_createDocWithMacro( final String _libName, final String _moduleName, final String _code ) throws Exception, IOException
+ {
+ // create an empty document
+ XModel databaseDoc = impl_createEmptyEmbeddedHSQLDocument();
+
+ // create Basic library/module therein
+ final XEmbeddedScripts embeddedScripts = UnoRuntime.queryInterface(XEmbeddedScripts.class, databaseDoc);
+ final XStorageBasedLibraryContainer basicLibs = embeddedScripts.getBasicLibraries();
+ final XNameContainer newLib = basicLibs.createLibrary( _libName );
+ newLib.insertByName( _moduleName, _code );
+
+ return databaseDoc;
+ }
+
+
+ /** Tests various aspects of database document "revenants"
+ *
+ * Well, I do not really have a good term for this... The point is, database documents are in real
+ * only *one* aspect of a more complex thing. The second aspect is a data source. Both, in some sense,
+ * just represent different views on the same thing. For a given database, there's at each time at most
+ * one data source, and at most one database document. Both have an independent life time, and are
+ * created when needed.
+ * In particular, a document can be closed (this is what happens when the last UI window displaying
+ * this document is closed), and then dies. Now when the other "view", the data source, still exists,
+ * the underlying document data is not discarded, but kept alive (else the data source would die
+ * just because the document dies, which is not desired). If the document is loaded, again, then
+ * it is re-created, using the data of its previous "incarnation".
+ *
+ * This method here tests some of those aspects of a document which should survive the death of one
+ * instance and re-creation as a revenant.
+ */
+ @Test
+ public void testDocumentRevenants() throws Exception, IOException
+ {
+ // create an empty document
+ XModel databaseDoc = impl_createDocWithMacro( "Lib", "Module",
+ "Sub Hello\n" +
+ " MsgBox \"Hello\"\n" +
+ "End Sub\n"
+ );
+ impl_storeDocument( databaseDoc );
+ final String documentURL = databaseDoc.getURL();
+
+ // at this stage, the marker should not yet be present in the doc's args, else some of the below
+ // tests become meaningless
+ assertTrue( "A newly created doc should not have the test case marker", !impl_hasMarker( databaseDoc.getArgs() ) );
+
+ // obtain the DataSource associated with the document. Keeping this alive
+ // ensures that the "impl data" of the document is kept alive, too, so when closing
+ // and re-opening it, this "impl data" must be re-used.
+ XDocumentDataSource dataSource = UnoRuntime.queryInterface(XDocumentDataSource.class, UnoRuntime.queryInterface(XOfficeDatabaseDocument.class, databaseDoc).getDataSource());
+
+ // close and reload the doc
+ impl_closeDocument(databaseDoc);
+ databaseDoc = impl_loadDocument( documentURL, impl_getMarkerLoadArgs() );
+ // since we just put the marker into the load-call, it should be present at the doc
+ assertTrue( "The test case marker got lost.", impl_hasMarker( databaseDoc.getArgs() ) );
+
+ // The basic library should have survived
+ final XEmbeddedScripts embeddedScripts = UnoRuntime.queryInterface(XEmbeddedScripts.class, databaseDoc);
+ final XStorageBasedLibraryContainer basicLibs = embeddedScripts.getBasicLibraries();
+ assertTrue( "Basic lib did not survive reloading a closed document", basicLibs.hasByName( "Lib" ) );
+ final XNameContainer lib = UnoRuntime.queryInterface(XNameContainer.class, basicLibs.getByName("Lib"));
+ assertTrue( "Basic module did not survive reloading a closed document", lib.hasByName( "Module" ) );
+
+ // now closing the doc, and obtaining it from the data source, should preserve the marker we put into the load
+ // args
+ impl_closeDocument( databaseDoc );
+ databaseDoc = UnoRuntime.queryInterface(XModel.class, dataSource.getDatabaseDocument());
+ assertTrue( "The test case marker did not survive re-retrieval of the doc from the data source.",
+ impl_hasMarker( databaseDoc.getArgs() ) );
+
+ // on the other hand, closing and regularly re-loading the doc *without* the marker should indeed
+ // lose it
+ impl_closeDocument( databaseDoc );
+ databaseDoc = impl_loadDocument( documentURL, impl_getDefaultLoadArgs() );
+ assertTrue( "Reloading the document kept the old args, instead of the newly supplied ones.",
+ !impl_hasMarker( databaseDoc.getArgs() ) );
+
+ // clean up
+ impl_closeDocument( databaseDoc );
+ }
+
+
+ @Test
+ public void testDocumentEvents() throws Exception, IOException
+ {
+ // create an empty document
+ final String libName = "EventHandlers";
+ final String moduleName = "all";
+ final String eventHandlerCode =
+ "Option Explicit\n" +
+ "\n" +
+ "Sub OnLoad\n" +
+ " Dim oCallback as Object\n" +
+ " oCallback = createUnoService( \"" + getCallbackComponentServiceName() + "\" )\n" +
+ "\n" +
+ " ' as long as the Document is not passed to the Basic callbacks, we need to create\n" +
+ " ' one ourself\n" +
+ " Dim oEvent as new com.sun.star.document.DocumentEvent\n" +
+ " oEvent.EventName = \"OnLoad\"\n" +
+ " oEvent.Source = ThisComponent\n" +
+ "\n" +
+ " oCallback.documentEventOccurred( oEvent )\n" +
+ "End Sub\n";
+ XModel databaseDoc = impl_createDocWithMacro( libName, moduleName, eventHandlerCode );
+ final String documentURL = databaseDoc.getURL();
+
+ // bind the macro to the OnLoad event
+ final String macroURI = "vnd.sun.star.script:" + libName + "." + moduleName + ".OnLoad?language=Basic&location=document";
+ final XEventsSupplier eventsSupplier = UnoRuntime.queryInterface(XEventsSupplier.class, databaseDoc);
+ eventsSupplier.getEvents().replaceByName("OnLoad", new PropertyValue[]
+ {
+ new PropertyValue("EventType", 0, "Script", PropertyState.DIRECT_VALUE),
+ new PropertyValue("Script", 0, macroURI, PropertyState.DIRECT_VALUE)
+ });
+
+ // store the document, and close it
+ impl_storeDocument( databaseDoc );
+ impl_closeDocument( databaseDoc );
+
+ // ensure the macro security configuration is "ask the user for document macro execution"
+ final int oldSecurityLevel = impl_setMacroSecurityLevel(1);
+
+ // load it, again
+ m_loadDocState = STATE_LOADING_DOC;
+ // expected order of states is:
+ // STATE_LOADING_DOC - initialized here
+ // STATE_MACRO_EXEC_APPROVED - done in our interaction handler, which auto-approves the execution of macros
+ // STATE_ON_LOAD_RECEIVED - done in our callback for the document events
+ //
+ // In particular, it is important that the interaction handler (which plays the role of the user confirmation
+ // here) is called before the OnLoad notification is received - since the latter happens from within
+ // a Basic macro which is bound to the OnLoad event of the document.
+
+ final String context = "OnLoad";
+ impl_startObservingEvents(context);
+ databaseDoc = impl_loadDocument( documentURL, impl_getMacroExecLoadArgs() );
+ impl_stopObservingEvents(m_documentEvents, new String[]
+ {
+ "OnLoad"
+ }, context);
+
+ assertEquals("our provided interaction handler was not called", STATE_ON_LOAD_RECEIVED, m_loadDocState);
+
+ // restore macro security level
+ impl_setMacroSecurityLevel(oldSecurityLevel);
+
+ // close the document
+ impl_closeDocument(databaseDoc);
+ }
+
+
+ @Test
+ public void testGlobalEvents() throws Exception, IOException
+ {
+ XModel databaseDoc = impl_createEmptyEmbeddedHSQLDocument();
+ final XStorable storeDoc = UnoRuntime.queryInterface(XStorable.class, databaseDoc);
+
+ String context, newURL;
+
+ // XStorable.store
+ final String oldURL = databaseDoc.getURL();
+ context = "store";
+ impl_startObservingEvents(context);
+ storeDoc.store();
+ assertEquals("store is not expected to change the document URL", databaseDoc.getURL(), oldURL);
+ impl_stopObservingEvents(m_globalEvents, new String[]
+ {
+ "OnSave", "OnSaveDone"
+ }, context);
+
+ // XStorable.storeToURL
+ context = "storeToURL";
+ impl_startObservingEvents(context);
+ storeDoc.storeToURL(createTempFileURL(), new PropertyValue[0]);
+ assertEquals("storetoURL is not expected to change the document URL", databaseDoc.getURL(), oldURL);
+ impl_stopObservingEvents(m_globalEvents, new String[]
+ {
+ "OnSaveTo", "OnSaveToDone"
+ }, context);
+
+ // XStorable.storeAsURL
+ newURL = createTempFileURL();
+ context = "storeAsURL";
+ impl_startObservingEvents(context);
+ storeDoc.storeAsURL(newURL, new PropertyValue[0]);
+ assertEquals("storeAsURL is expected to change the document URL", databaseDoc.getURL(), newURL);
+ impl_stopObservingEvents(m_globalEvents, new String[]
+ {
+ "OnSaveAs", "OnSaveAsDone"
+ }, context);
+
+ // XModifiable.setModified
+ final XModifiable modifyDoc = UnoRuntime.queryInterface(XModifiable.class, databaseDoc);
+ context = "setModified";
+ impl_startObservingEvents(context);
+ modifyDoc.setModified(true);
+ assertEquals("setModified didn't work", modifyDoc.isModified(), true);
+ impl_stopObservingEvents(m_globalEvents, new String[]
+ {
+ "OnModifyChanged"
+ }, context);
+
+ // XStorable.store, with implicit reset of the "Modified" flag
+ context = "store (2)";
+ impl_startObservingEvents(context);
+ storeDoc.store();
+ assertEquals("'store' should implicitly reset the modified flag", modifyDoc.isModified(), false);
+ impl_stopObservingEvents(m_globalEvents, new String[]
+ {
+ "OnSave", "OnSaveDone", "OnModifyChanged"
+ }, context);
+
+ // XComponentLoader.loadComponentFromURL
+ newURL = copyToTempFile(databaseDoc.getURL());
+ final XComponentLoader loader = UnoRuntime.queryInterface(XComponentLoader.class, getMSF().createInstance("com.sun.star.frame.Desktop"));
+ context = "loadComponentFromURL";
+ impl_startObservingEvents(context);
+ databaseDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(newURL, _BLANK, 0, impl_getDefaultLoadArgs()));
+ impl_stopObservingEvents(m_globalEvents,
+ new String[]
+ {
+ "OnLoadFinished", "OnViewCreated", "OnFocus", "OnLoad"
+ }, context);
+
+ // closing a document by API
+ final XCloseable closeDoc = UnoRuntime.queryInterface(XCloseable.class, databaseDoc);
+ context = "close (API)";
+ impl_startObservingEvents(context);
+ closeDoc.close(true);
+ impl_stopObservingEvents(m_globalEvents,
+ new String[]
+ {
+ "OnPrepareUnload", "OnViewClosed", "OnUnload"
+ }, context);
+
+ // closing a document via UI
+ context = "close (UI)";
+ impl_startObservingEvents("prepare for '" + context + "'");
+ databaseDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(newURL, _BLANK, 0, impl_getDefaultLoadArgs()));
+ impl_waitForEvent(m_globalEvents, "OnLoad", 5000);
+ // wait for all events to arrive - OnLoad should be the last one
+
+ final XDispatchProvider dispatchProvider = UnoRuntime.queryInterface(XDispatchProvider.class, databaseDoc.getCurrentController().getFrame());
+ final URL url = impl_getURL(".uno:CloseDoc");
+ final XDispatch dispatcher = dispatchProvider.queryDispatch(url, "", 0);
+ impl_startObservingEvents(context);
+ dispatcher.dispatch(url, new PropertyValue[0]);
+ impl_stopObservingEvents(m_globalEvents,
+ new String[]
+ {
+ "OnPrepareViewClosing", "OnViewClosed", "OnPrepareUnload", "OnUnload"
+ }, context);
+
+ // creating a new document
+ databaseDoc = impl_createDocument();
+ final XLoadable loadDoc = UnoRuntime.queryInterface(XLoadable.class, databaseDoc);
+ context = "initNew";
+ impl_startObservingEvents(context);
+ loadDoc.initNew();
+ impl_stopObservingEvents(m_globalEvents, new String[]
+ {
+ "OnCreate"
+ }, context);
+
+ impl_startObservingEvents(context + " (cleanup)");
+ impl_closeDocument(databaseDoc);
+ impl_waitForEvent(m_globalEvents, "OnUnload", 5000);
+
+ // focus changes
+ context = "activation";
+ // for this, load a database document ...
+ impl_startObservingEvents("prepare for '" + context + "'");
+ databaseDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(newURL, _BLANK, 0, impl_getDefaultLoadArgs()));
+ final int previousOnLoadEventPos = impl_waitForEvent(m_globalEvents, "OnLoad", 5000);
+ // ... and another document ...
+ final String otherURL = copyToTempFile(databaseDoc.getURL());
+ final XModel otherDoc = UnoRuntime.queryInterface(XModel.class, loader.loadComponentFromURL(otherURL, _BLANK, 0, impl_getDefaultLoadArgs()));
+ impl_raise(otherDoc);
+ impl_waitForEvent(m_globalEvents, "OnLoad", 5000, previousOnLoadEventPos + 1);
+
+ // ... and switch between the two
+ impl_startObservingEvents(context);
+ impl_raise(databaseDoc);
+ impl_stopObservingEvents(m_globalEvents, new String[]
+ {
+ "OnUnfocus", "OnFocus"
+ }, context);
+
+ // cleanup
+ impl_startObservingEvents("cleanup after '" + context + "'");
+ impl_closeDocument(databaseDoc);
+ impl_closeDocument(otherDoc);
+ }
+
+
+ private URL impl_getURL(String _completeURL) throws Exception
+ {
+ final URL[] url =
+ {
+ new URL()
+ };
+ url[0].Complete = _completeURL;
+ final XURLTransformer urlTransformer = UnoRuntime.queryInterface(XURLTransformer.class, getMSF().createInstance("com.sun.star.util.URLTransformer"));
+ urlTransformer.parseStrict(url);
+ return url[0];
+ }
+
+
+ private void impl_raise(XModel _document)
+ {
+ final XFrame frame = _document.getCurrentController().getFrame();
+ final XTopWindow topWindow = UnoRuntime.queryInterface(XTopWindow.class, frame.getContainerWindow());
+ topWindow.toFront();
+ }
+
+
+ private void impl_startObservingEvents(String _context)
+ {
+ System.out.println(" " + _context + " {");
+ synchronized (m_documentEvents)
+ {
+ m_documentEvents.clear();
+ }
+ synchronized (m_globalEvents)
+ {
+ m_globalEvents.clear();
+ }
+ }
+
+
+ private void impl_stopObservingEvents(ArrayList<String> _actualEvents, String[] _expectedEvents, String _context)
+ {
+ try
+ {
+ synchronized (_actualEvents)
+ {
+ int actualEventCount = _actualEvents.size();
+ while (actualEventCount < _expectedEvents.length)
+ {
+ // well, it's possible not all events already arrived, yet - finally, some of them
+ // are notified asynchronously
+ // So, wait a few seconds.
+ try
+ {
+ _actualEvents.wait(20000);
+ }
+ catch (InterruptedException ex)
+ {
+ }
+
+ if (actualEventCount == _actualEvents.size())
+ // the above wait was left because of the timeout, *not* because an event
+ // arrived. Okay, we won't wait any longer, this is a failure.
+ {
+ break;
+ }
+ actualEventCount = _actualEvents.size();
+ }
+
+ assertEquals("wrong event count for '" + _context + "'",
+ _expectedEvents.length, _actualEvents.size());
+
+ for (int i = 0; i < _expectedEvents.length; ++i)
+ {
+ assertEquals("wrong event at position " + (i + 1) + " for '" + _context + "'",
+ _expectedEvents[i], _actualEvents.get(i));
+ }
+ }
+ }
+ finally
+ {
+ System.out.println(" }");
+ }
+ }
+
+
+ private int impl_waitForEvent(ArrayList<String> _eventQueue, String _expectedEvent, int _maxMilliseconds)
+ {
+ return impl_waitForEvent(_eventQueue, _expectedEvent, _maxMilliseconds, 0);
+ }
+
+
+ private int impl_waitForEvent(ArrayList<String> _eventQueue, String _expectedEvent, int _maxMilliseconds, int _firstQueueElementToCheck)
+ {
+ synchronized (_eventQueue)
+ {
+ int waitedMilliseconds = 0;
+
+ while (waitedMilliseconds < _maxMilliseconds)
+ {
+ for (int i = _firstQueueElementToCheck; i < _eventQueue.size(); ++i)
+ {
+ if (_expectedEvent.equals(_eventQueue.get(i)))
+ // found the event in the queue
+ {
+ return i;
+ }
+ }
+
+ // wait a little, perhaps the event will still arrive
+ try
+ {
+ _eventQueue.wait(500);
+ waitedMilliseconds += 500;
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+ }
+
+ fail("expected event '" + _expectedEvent + "' did not arrive after " + _maxMilliseconds + " milliseconds");
+ return -1;
+ }
+
+
+ private void onDocumentEvent(DocumentEvent _Event)
+ {
+ if ("OnTitleChanged".equals(_Event.EventName))
+ // OnTitleChanged events are notified too often. This is known, and accepted.
+ // (the deeper reason is that it's difficult to determine, in the DatabaseDocument implementation,
+ // when the title actually changed. In particular, when we do a saveAsURL, and then ask for a
+ // title *before* the TitleHelper got the document's OnSaveAsDone event, then the wrong (old)
+ // title is obtained.
+ {
+ return;
+ }
+
+ if ((_Event.EventName.equals("OnLoad")) && (m_loadDocState != STATE_NOT_STARTED))
+ {
+ assertEquals("OnLoad event must come *after* invocation of the interaction handler / user!",
+ m_loadDocState, STATE_MACRO_EXEC_APPROVED);
+ m_loadDocState = STATE_ON_LOAD_RECEIVED;
+ }
+
+ synchronized (m_documentEvents)
+ {
+ m_documentEvents.add(_Event.EventName);
+ m_documentEvents.notifyAll();
+ }
+
+ System.out.println(" document event: " + _Event.EventName);
+ }
+
+
+ public void documentEventOccured(DocumentEvent _Event)
+ {
+ if ("OnTitleChanged".equals(_Event.EventName))
+ // ignore. See onDocumentEvent for a justification
+ {
+ return;
+ }
+
+ synchronized (m_globalEvents)
+ {
+ m_globalEvents.add(_Event.EventName);
+ m_globalEvents.notifyAll();
+ }
+
+ System.out.println(" global event: " + _Event.EventName);
+ }
+
+
+ public void disposing(EventObject _Event)
+ {
+ // not interested in
+ }
+}
diff --git a/dbaccess/qa/complex/dbaccess/FileHelper.java b/dbaccess/qa/complex/dbaccess/FileHelper.java
new file mode 100644
index 0000000000..cae67af008
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/FileHelper.java
@@ -0,0 +1,35 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package complex.dbaccess;
+
+public class FileHelper
+{
+ private FileHelper(){}
+ public static String getOOoCompatibleFileURL( String _javaFileURL )
+ {
+ String returnURL = _javaFileURL;
+ if ( ( returnURL.indexOf( "file:/" ) == 0 ) && ( returnURL.indexOf( "file:///" ) == -1 ) )
+ {
+ // for some reason, the URLs here in Java start with "file:/" only, instead of "file:///"
+ // Some of the office code doesn't like this ...
+ returnURL = "file:///" + returnURL.substring( 6 );
+ }
+ return returnURL;
+ }
+}
diff --git a/dbaccess/qa/complex/dbaccess/Parser.java b/dbaccess/qa/complex/dbaccess/Parser.java
new file mode 100644
index 0000000000..8025e1d379
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/Parser.java
@@ -0,0 +1,183 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package complex.dbaccess;
+
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.container.XIndexAccess;
+import com.sun.star.sdb.XParametersSupplier;
+import com.sun.star.sdb.XSingleSelectQueryComposer;
+import com.sun.star.sdbc.DataType;
+import com.sun.star.sdbc.SQLException;
+import com.sun.star.uno.UnoRuntime;
+
+
+// ---------- junit imports -----------------
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+
+public class Parser extends CRMBasedTestCase
+{
+
+ @Override
+ protected void createTestCase() throws Exception
+ {
+ super.createTestCase();
+ m_database.getDatabase().getDataSource().createQuery( "query products", "SELECT * FROM \"products\"" );
+ }
+
+
+ @Test
+ public void checkWhere() throws Exception
+ {
+ final XSingleSelectQueryComposer composer = createQueryComposer();
+ final String SELECT = "SELECT \"products\".\"Name\" FROM \"products\" WHERE ";
+ final String[] queries = new String[]
+ {
+ "\"ID\" in ( 1,2,3,4)"
+ ,"not ( \"ID\" in ( 1,2,3,4))"
+ ,"(1 = 1) is true"
+ ,"(1 = 1) is not false"
+ ,"(1 = 1) is not null"
+ ,"not ( (1 = 1) is not null)"
+ ,"'a' like 'a%'"
+ ,"not ( 'a' like 'a%')"
+ ,"'a' not like 'a%'"
+ ,"1 between 0 and 2"
+ ,"not ( 1 between 0 and 2 )"
+ ,"1 not between 3 and 4"
+ ,"1 not between ( select \"ID\" from \"categories\") and ( select \"ID\" from \"categories\")"
+ ,"1 = 1"
+ ,"0 < 1"
+ ,"not(0 < 1)"
+ ,"1 > 0"
+ ,"not(1 > 0)"
+ ,"1 <> 0"
+ ,"(1 <> 0 and 'a' = 'a' and 'c' = 'd') or (1 = 1 and 2 = 2 and 3 = 4)"
+ ,"not ( 1 <> 0 )"
+ ,"\"CategoryID\" in ( select \"ID\" from \"categories\")"
+ ,"not (\"CategoryID\" in ( select \"ID\" from \"categories\"))"
+ ,"\"CategoryID\" not in ( select \"ID\" from \"categories\")"
+ };
+ for (int i = 0; i < queries.length; i++)
+ {
+ composer.setQuery( SELECT + queries[i]);
+ }
+ }
+
+ /** verifies that aliases for inner queries work as expected
+ */
+ @Test
+ public void checkJoinSyntax() throws Exception
+ {
+ final XSingleSelectQueryComposer composer = createQueryComposer();
+
+ // feed the composer with some statements. If any of those cannot be parsed, the composer
+ // will throw an exception - which is a regression then
+ composer.setQuery(
+ "SELECT \"categories\".\"Name\", " +
+ "\"products\".\"Name\" " +
+ "FROM \"products\" RIGHT OUTER JOIN \"categories\" AS \"categories\" ON \"products\".\"CategoryID\" = \"categories\".\"ID\"" );
+
+ composer.setQuery(
+ "SELECT \"categories\".\"Name\", " +
+ "\"products\".\"Name\" " +
+ "FROM \"products\" LEFT OUTER JOIN \"categories\" AS \"categories\" ON \"products\".\"CategoryID\" = \"categories\".\"ID\"" );
+
+ composer.setQuery(
+ "SELECT \"categories\".\"Name\", " +
+ "\"products\".\"Name\" " +
+ "FROM \"products\" CROSS JOIN \"categories\" AS \"categories\"" );
+
+ composer.setQuery(
+ "SELECT \"categories\".\"Name\", " +
+ "\"products\".\"Name\" " +
+ "FROM \"products\" INNER JOIN \"categories\" AS \"categories\" ON \"products\".\"CategoryID\" = \"categories\".\"ID\"" );
+
+ // just to be sure the composer *really* parses upon setting the query: feed it with
+ // an unparseable statement
+ boolean caughtExpected = false;
+ try
+ {
+ composer.setQuery( "NONSENSE" );
+ }
+ catch( SQLException e )
+ {
+ caughtExpected = true;
+ }
+ assertTrue( "pre-condition not met: parser should except on unparseable statements, else the complete" +
+ "test is bogus!", caughtExpected );
+ }
+
+
+ private void impl_checkParameters( final String _statement, final String[] _expectedParameterNames, final int[] _expectedParameterTypes,final String _context ) throws Exception
+ {
+ final XSingleSelectQueryComposer composer = createQueryComposer();
+ composer.setQuery( _statement );
+
+ assertEquals( "checkParameterTypes: internal error", _expectedParameterNames.length, _expectedParameterTypes.length );
+
+ final XParametersSupplier paramSupp = UnoRuntime.queryInterface(XParametersSupplier.class, composer);
+ final XIndexAccess parameters = paramSupp.getParameters();
+
+ assertEquals( "(ctx: " + _context + ") unexpected parameter count", _expectedParameterNames.length, parameters.getCount() );
+ for ( int i=0; i<parameters.getCount(); ++i )
+ {
+ final XPropertySet parameter = UnoRuntime.queryInterface(XPropertySet.class, parameters.getByIndex(i));
+
+ final String name = (String)parameter.getPropertyValue( "Name" );
+ assertEquals( "(ctx: " + _context + ") unexpected parameter name for parameter number " + ( i + 1 ), _expectedParameterNames[i], name );
+
+ final int type = ((Integer)parameter.getPropertyValue( "Type" )).intValue();
+ assertEquals( "(ctx: " + _context + ") unexpected data type for parameter number " + ( i + 1 ), _expectedParameterTypes[i], type );
+ }
+ }
+
+
+ /** verifies that the parser properly recognizes the types of parameters
+ */
+ @Test
+ public void checkParameterTypes() throws Exception
+ {
+ impl_checkParameters(
+ "SELECT * FROM \"all orders\" " +
+ "WHERE ( \"Order Date\" >= :order_date ) " +
+ " AND ( ( \"Customer Name\" LIKE :customer ) " +
+ " OR ( \"Product Name\" LIKE ? ) " +
+ " )",
+ new String[] { "order_date", "customer", "Product Name" },
+ new int[] { DataType.DATE, DataType.VARCHAR, DataType.VARCHAR },
+ ">= && LIKE"
+ );
+
+ impl_checkParameters(
+ "SELECT * FROM \"categories\" " +
+ "WHERE \"ID\" BETWEEN :id_lo AND :id_hi",
+ new String[] { "id_lo", "id_hi" },
+ new int[] { DataType.INTEGER, DataType.INTEGER },
+ "BETWEEN"
+ );
+
+ impl_checkParameters(
+ "SELECT CONCAT( :prefix, CONCAT( \"Name\", :suffix ) ) FROM \"customers\"",
+ new String[] { "prefix", "suffix" },
+ new int[] { DataType.VARCHAR, DataType.VARCHAR },
+ "CONCAT"
+ );
+ }
+}
diff --git a/dbaccess/qa/complex/dbaccess/PropertyBag.java b/dbaccess/qa/complex/dbaccess/PropertyBag.java
new file mode 100644
index 0000000000..6807569ec5
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/PropertyBag.java
@@ -0,0 +1,302 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package complex.dbaccess;
+
+import com.sun.star.beans.NamedValue;
+import com.sun.star.beans.PropertyState;
+import com.sun.star.beans.PropertyValue;
+import com.sun.star.beans.PropertyAttribute;
+import com.sun.star.beans.XPropertyAccess;
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.beans.XPropertyContainer;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.lang.XMultiServiceFactory;
+
+// ---------- junit imports -----------------
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+
+public class PropertyBag extends TestCase
+{
+ private static final String VALUE = "Value";
+ private XPropertyContainer m_bag;
+ private XPropertySet m_set;
+ private XPropertyAccess m_access;
+ private XMultiServiceFactory m_orb = null;
+
+ public String getTestObjectName()
+ {
+ return "PropertyBag";
+ }
+
+ @Before
+ @Override
+ public void before()
+ {
+ m_orb = getMSF();
+ }
+
+ @Test
+ public void checkBasics() throws Exception
+ {
+ createEmptyBag();
+ System.out.println("testing the basics");
+
+ // check whether empty property names are rejected
+ boolean caughtExpected = false;
+ try
+ {
+ m_bag.addProperty( "", PropertyAttribute.BOUND, Integer.valueOf( 3 ) );
+ }
+ catch(com.sun.star.lang.IllegalArgumentException e) { caughtExpected = true; }
+ catch(com.sun.star.uno.Exception e) { }
+ if ( !caughtExpected )
+ {
+ fail("empty property names are not rejected by XPropertyContainer::addProperty");
+ }
+
+ // check whether duplicate insertions are rejected
+ caughtExpected = false;
+ try
+ {
+ m_bag.addProperty( VALUE, PropertyAttribute.BOUND, "" );
+ m_bag.addProperty( VALUE, PropertyAttribute.BOUND, "" );
+ }
+ catch(com.sun.star.beans.PropertyExistException e) { caughtExpected = true; }
+ catch(com.sun.star.uno.Exception e) { }
+ if ( !caughtExpected )
+ {
+ fail("insertion of duplicate property names is not rejected");
+ }
+
+ // try removing the property we just added - this should fail, as it does not have
+ // the REMOVABLE attribute
+ caughtExpected = false;
+ try
+ {
+ m_bag.removeProperty( VALUE);
+ }
+ catch(com.sun.star.beans.NotRemoveableException e) { caughtExpected = true; }
+ catch(com.sun.star.uno.Exception e) { }
+ if ( !caughtExpected )
+ {
+ fail("removing non-removable properties is expected to fail - but it didn't");
+ }
+
+ // try removing a non-existent property
+ caughtExpected = false;
+ try
+ {
+ m_bag.removeProperty( "NonExistent" );
+ }
+ catch(com.sun.star.beans.UnknownPropertyException e) { caughtExpected = true; }
+ catch(com.sun.star.uno.Exception e) { }
+ if ( !caughtExpected )
+ {
+ fail("removing non-existent properties is expected to fail - but it didn't");
+ }
+
+ // try writing and reading a value for the one property we have so far
+ final String testValue = "someArbitraryValue";
+ m_set.setPropertyValue( VALUE , testValue);
+ final String currentValue = (String)m_set.getPropertyValue( VALUE);
+ if ( !currentValue.equals( testValue ) )
+ {
+ fail("set property is not remembered");
+ }
+
+ // try setting an illegal value for the property
+ caughtExpected = false;
+ try
+ {
+ m_set.setPropertyValue( VALUE, Integer.valueOf( 3 ) );
+ }
+ catch(com.sun.star.lang.IllegalArgumentException e) { caughtExpected = true; }
+ catch(com.sun.star.uno.Exception e) { }
+ if ( !caughtExpected )
+ {
+ fail("the bag does not respect the property type we declared for the property");
+ }
+ }
+
+ @Test
+ public void checkSequenceAccess() throws com.sun.star.uno.Exception
+ {
+ System.out.println( "checking PropertySetAccess via sequences" );
+ createStandardBag( false );
+
+
+ // XPropertyAccess.setPropertyValues
+ final PropertyValue expectedValues[] =
+ {
+ new PropertyValue( "BoolValue", -1, Boolean.FALSE, PropertyState.DIRECT_VALUE ),
+ new PropertyValue( "StringValue", -1, "some text", PropertyState.DIRECT_VALUE ),
+ new PropertyValue( "IntegerValue", -1, Integer.valueOf( 3 ), PropertyState.DIRECT_VALUE ),
+ new PropertyValue( "InterfaceValue", -1, m_bag, PropertyState.DIRECT_VALUE )
+ };
+ m_access.setPropertyValues( expectedValues );
+
+ for ( int i=0; i<expectedValues.length; ++i )
+ {
+ final Object value = m_set.getPropertyValue( expectedValues[i].Name );
+ if ( !value.equals( expectedValues[i].Value ) )
+ {
+ System.out.println( "property name : " + expectedValues[i].Name );
+ System.out.println( "expected value: " + expectedValues[i].Value.toString() );
+ System.out.println( "current value : " + value.toString() );
+ fail( "retrieving a previously set property (" + expectedValues[i].Value.getClass().toString() + ") failed" );
+ }
+ }
+
+
+ // XPropertyAccess.getPropertyValues
+ final PropertyValue currentValues[] = m_access.getPropertyValues();
+ for ( int i=0; i<currentValues.length; ++i )
+ {
+ final String name = currentValues[i].Name;
+ final Object value = currentValues[i].Value;
+ for ( int j=0; j<expectedValues.length; ++j )
+ {
+ if ( expectedValues[j].Name.equals( name ) )
+ {
+ if ( !expectedValues[j].Value.equals( value ) )
+ {
+ System.out.println( "property name : " + expectedValues[j].Name );
+ System.out.println( "expected value: " + expectedValues[j].Value.toString() );
+ System.out.println( "current value : " + value.toString() );
+ fail( "getPropertyValues failed for property '" + name + "' failed" );
+ }
+ break;
+ }
+ }
+
+ if ( !m_set.getPropertyValue( name ).equals( value ) )
+ {
+ fail("XPropertyAccess::getPropertyValues() and XPropertyset::getPropertyValue results are inconsistent");
+ }
+ }
+ }
+
+ @Test
+ public void checkDynamicSet()
+ {
+ System.out.println( "checking proper dynamic of the set" );
+ createStandardBag( false );
+
+ final PropertyValue props[] =
+ {
+ new PropertyValue( "BoolValue", -1, Boolean.FALSE, PropertyState.DIRECT_VALUE),
+ new PropertyValue( "StringValue", -1, "test", PropertyState.DIRECT_VALUE ),
+ new PropertyValue( "SomeOtherStringValue", -1, "string value", PropertyState.DIRECT_VALUE )
+ };
+
+ // try setting some property values which are not existent
+ boolean caughtExpected = false;
+ try
+ {
+ m_access.setPropertyValues( props );
+ }
+ catch( com.sun.star.beans.UnknownPropertyException e ) { caughtExpected = true; }
+ catch( com.sun.star.uno.Exception e ) { }
+ if ( !caughtExpected )
+ {
+ fail("the set shouldn't accept unknown property values, if not explicitly told to do so");
+ }
+
+ // re-create the bag, this time allow it to implicitly add properties
+ createStandardBag( true );
+ boolean success = false;
+ try { m_access.setPropertyValues( props ); success = true; }
+ catch( com.sun.star.uno.Exception e ) { }
+ if ( !success )
+ {
+ fail("property bag failed to implicitly add unknown properties");
+ }
+
+ // see whether this property was really added, and not just ignored
+ final PropertyValue newlyAdded = props[ props.length - 1 ];
+ try
+ {
+ if ( !m_set.getPropertyValue( newlyAdded.Name ).equals( newlyAdded.Value ) )
+ {
+ fail("the new property was not really added, or not added with the proper value");
+ }
+ }
+ catch( com.sun.star.uno.Exception e ) { }
+ }
+
+ private void createEmptyBag()
+ {
+ try
+ {
+ m_bag = null;
+ final String serviceName = "com.sun.star.beans.PropertyBag";
+ m_bag = UnoRuntime.queryInterface(XPropertyContainer.class, m_orb.createInstance(serviceName));
+ if ( m_bag == null )
+ {
+ fail("could not create a " + serviceName + " instance");
+ }
+ m_set = UnoRuntime.queryInterface(XPropertySet.class, m_bag);
+ m_access = UnoRuntime.queryInterface(XPropertyAccess.class, m_bag);
+ }
+ catch( com.sun.star.uno.Exception e )
+ {
+ }
+ }
+
+ private void createStandardBag( boolean allowLazyAdding )
+ {
+ try
+ {
+ m_bag = null;
+
+ final Object initArgs[] = { new NamedValue( "AutomaticAddition", Boolean.valueOf( allowLazyAdding ) ) };
+
+ final String serviceName = "com.sun.star.beans.PropertyBag";
+ m_bag = UnoRuntime.queryInterface(XPropertyContainer.class, m_orb.createInstanceWithArguments(serviceName, initArgs));
+ if ( m_bag == null )
+ {
+ fail("could not create a " + serviceName + " instance");
+ }
+ m_set = UnoRuntime.queryInterface(XPropertySet.class, m_bag);
+ m_access = UnoRuntime.queryInterface(XPropertyAccess.class, m_bag);
+
+ final Object properties[][] =
+ {
+ { "BoolValue", Boolean.TRUE },
+ { "StringValue", "" },
+ { "IntegerValue", Integer.valueOf( 3 ) },
+ { "InterfaceValue", m_bag }
+ };
+ for ( int i=0; i<properties.length; ++i )
+ {
+ m_bag.addProperty(
+ (String)properties[i][0],
+ PropertyAttribute.MAYBEVOID,
+ properties[i][1]
+ );
+ }
+ }
+ catch( com.sun.star.uno.Exception e )
+ {
+ }
+ }
+}
diff --git a/dbaccess/qa/complex/dbaccess/Query.java b/dbaccess/qa/complex/dbaccess/Query.java
new file mode 100644
index 0000000000..8881bb3ede
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/Query.java
@@ -0,0 +1,107 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package complex.dbaccess;
+
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.container.XIndexAccess;
+import com.sun.star.container.XNameAccess;
+import com.sun.star.container.XNamed;
+import com.sun.star.sdb.XQueriesSupplier;
+import com.sun.star.sdbcx.XColumnsSupplier;
+import com.sun.star.uno.UnoRuntime;
+import connectivity.tools.CRMDatabase;
+
+// ---------- junit imports -----------------
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+
+public class Query extends TestCase
+{
+
+ connectivity.tools.HsqlDatabase m_database;
+
+
+ private void createTestCase()
+ {
+ try
+ {
+ if (m_database == null)
+ {
+ final CRMDatabase database = new CRMDatabase(getMSF(), false);
+ m_database = database.getDatabase();
+ }
+ }
+ catch (Exception e)
+ {
+ System.out.println("could not create the test case, error message:\n" + e.getMessage());
+ e.printStackTrace(System.err);
+ fail("failed to created the test case");
+ }
+ }
+
+
+ @Test
+ public void testQueryColumns()
+ {
+ createTestCase();
+
+ try
+ {
+ final XQueriesSupplier suppQueries = UnoRuntime.queryInterface(
+ XQueriesSupplier.class, m_database.defaultConnection().getXConnection() );
+ final XNameAccess queries = suppQueries.getQueries();
+
+ final String[] queryNames = new String[] { "parseable", "parseable native", "unparseable" };
+ final String[][] expectedColumnNames = new String[][] {
+ new String[] { "ID", "Name", "Address", "City", "Postal","Comment" },
+ new String[] { "TABLE_CATALOG", "TABLE_SCHEMA", "TABLE_NAME", "VIEW_DEFINITION", "CHECK_OPTION", "IS_UPDATABLE", "VALID" },
+ new String[] { "ID_VARCHAR" }
+ };
+
+ for ( int i = 0; i < queryNames.length; ++i )
+ {
+ if (queries.hasByName(queryNames[i]))
+ {
+ final XPropertySet query = UnoRuntime.queryInterface(
+ XPropertySet.class, queries.getByName( queryNames[i] ) );
+
+ final XColumnsSupplier suppCols = UnoRuntime.queryInterface(
+ XColumnsSupplier.class, query);
+ final XIndexAccess columns = UnoRuntime.queryInterface(
+ XIndexAccess.class, suppCols.getColumns());
+
+ // check whether the columns supplied by the query match what we expected
+ assertTrue( "invalid column count (found " + columns.getCount() + ", expected: " + expectedColumnNames[i].length + ") for query \"" + queryNames[i] + "\"",
+ columns.getCount() == expectedColumnNames[i].length );
+ for ( int col = 0; col < columns.getCount(); ++col )
+ {
+ final XNamed columnName = UnoRuntime.queryInterface(
+ XNamed.class, columns.getByIndex(col) );
+ assertTrue( "column no. " + col + " of query \"" + queryNames[i] + "\" not matching",
+ columnName.getName().equals( expectedColumnNames[i][col] ) );
+ }
+ }
+ }
+ }
+ catch ( Exception e )
+ {
+ fail( "caught an unexpected exception: " + e.getMessage() );
+ }
+ }
+}
diff --git a/dbaccess/qa/complex/dbaccess/QueryInQuery.java b/dbaccess/qa/complex/dbaccess/QueryInQuery.java
new file mode 100644
index 0000000000..4e6a9d0ffe
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/QueryInQuery.java
@@ -0,0 +1,177 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package complex.dbaccess;
+
+import com.sun.star.container.ElementExistException;
+import com.sun.star.lang.IllegalArgumentException;
+import com.sun.star.lang.WrappedTargetException;
+import com.sun.star.sdb.CommandType;
+import com.sun.star.sdbc.SQLException;
+import connectivity.tools.HsqlColumnDescriptor;
+import connectivity.tools.HsqlTableDescriptor;
+import connectivity.tools.RowSet;
+import com.sun.star.sdbc.XStatement;
+import com.sun.star.sdbc.XResultSet;
+
+// ---------- junit imports -----------------
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+
+public class QueryInQuery extends CRMBasedTestCase
+{
+ private static final String QUERY_PRODUCTS = "query products";
+
+
+ @Override
+ protected void createTestCase()
+ {
+ try
+ {
+ super.createTestCase();
+ m_database.getDatabase().getDataSource().createQuery( QUERY_PRODUCTS,"SELECT * FROM \"products\"");
+ }
+ catch ( Exception e )
+ {
+ e.printStackTrace( System.err );
+ fail( "caught an exception (" + e.getMessage() + ") while creating the test case" );
+ }
+ }
+
+
+ private void verifyEqualRowSetContent( int _outerCommandType, String _outerCommand, int _innerCommandType, String _innerCommand ) throws SQLException
+ {
+ final RowSet outerRowSet = m_database.getDatabase().createRowSet( _outerCommandType, _outerCommand );
+ outerRowSet.execute();
+
+ final RowSet innerRowSet = m_database.getDatabase().createRowSet( _innerCommandType, _innerCommand );
+ innerRowSet.execute();
+
+ outerRowSet.last();
+ innerRowSet.last();
+ assertTrue( "wrong record counts", outerRowSet.getRow() == innerRowSet.getRow() );
+
+ outerRowSet.beforeFirst();
+ innerRowSet.beforeFirst();
+ assertTrue( "wrong column counts", outerRowSet.getColumnCount() == innerRowSet.getColumnCount() );
+
+ while ( outerRowSet.next() && innerRowSet.next() )
+ {
+ for ( int i=1; i <= outerRowSet.getColumnCount(); ++i )
+ {
+ assertTrue( "content of column " + i + " of row " + outerRowSet.getRow() + " not identical",
+ innerRowSet.getString(i).equals( outerRowSet.getString(i) ) );
+ }
+ }
+ }
+
+
+ /** executes a SQL statement simply selecting all columns from a query
+ */
+ @Test
+ public void executeSimpleSelect() throws SQLException
+ {
+ verifyEqualRowSetContent(
+ CommandType.COMMAND, "SELECT * FROM \"query products\"",
+ CommandType.QUERY,QUERY_PRODUCTS);
+ }
+
+
+ /** verifies that aliases for inner queries work as expected
+ */
+ @Test
+ public void executeAliasedSelect() throws SQLException
+ {
+ verifyEqualRowSetContent(
+ CommandType.COMMAND, "SELECT \"PROD\".\"ID\" FROM \"query products\" AS \"PROD\"",
+ CommandType.COMMAND, "SELECT \"ID\" FROM \"products\"" );
+ verifyEqualRowSetContent(
+ CommandType.COMMAND, "SELECT \"PROD\".* FROM \"query products\" AS \"PROD\"",
+ CommandType.QUERY,QUERY_PRODUCTS);
+ }
+
+
+ /** verifies that aliases for inner queries work as expected
+ */
+ @Test
+ public void checkNameCollisions()
+ {
+ // create a query with a name which is used by a table
+ boolean caughtExpected = false;
+ try
+ {
+ m_database.getDatabase().getDataSource().createQuery( "products", "SELECT * FROM \"products\"" );
+ }
+ catch ( WrappedTargetException e ) { caughtExpected = true; }
+ catch ( IllegalArgumentException e ) {}
+ catch ( ElementExistException e ) { caughtExpected = true; }
+ assertTrue( "creating queries with the name of an existing table should not be possible",
+ caughtExpected );
+
+ // create a table with a name which is used by a query
+ final HsqlTableDescriptor table = new HsqlTableDescriptor( QUERY_PRODUCTS,
+ new HsqlColumnDescriptor[] {
+ new HsqlColumnDescriptor( "ID", "INTEGER" ),
+ new HsqlColumnDescriptor( "Name", "VARCHAR(50)" ) } );
+
+ caughtExpected = false;
+ try
+ {
+ m_database.getDatabase().createTableInSDBCX( table );
+ }
+ catch ( SQLException e ) { caughtExpected = true; }
+ catch ( ElementExistException ex ) { }
+ assertTrue( "creating tables with the name of an existing query should not be possible",
+ caughtExpected );
+ }
+
+
+ @Test
+ public void checkCyclicReferences() throws ElementExistException, WrappedTargetException, IllegalArgumentException
+ {
+ // some queries which create a cycle in the sub query tree
+ m_database.getDatabase().getDataSource().createQuery( "orders level 1", "SELECT * FROM \"orders level 0\"" );
+ m_database.getDatabase().getDataSource().createQuery( "orders level 2", "SELECT * FROM \"orders level 1\"" );
+ m_database.getDatabase().getDataSource().createQuery( "orders level 3", "SELECT * FROM \"orders level 2\"" );
+ m_database.getDatabase().getDataSource().createQuery( "orders level 0", "SELECT * FROM \"orders level 3\"" );
+
+ final RowSet rowSet = m_database.getDatabase().createRowSet( CommandType.QUERY, "orders level 0" );
+
+ boolean caughtExpected = false;
+ try { rowSet.execute(); }
+ catch ( SQLException e ) { caughtExpected = ( e.ErrorCode == -com.sun.star.sdb.ErrorCondition.PARSER_CYCLIC_SUB_QUERIES ); }
+
+ assertTrue( "executing a query with cyclic nested sub queries should fail!", caughtExpected );
+ }
+
+
+ @Test
+ public void checkStatementQiQSupport()
+ {
+ try
+ {
+ final XStatement statement = m_database.getConnection().createStatement();
+ final XResultSet resultSet = statement.executeQuery( "SELECT * FROM \"query products\"" );
+ assertTrue( "Result Set is null", resultSet != null );
+ }
+ catch( SQLException e )
+ {
+ fail( "SDB level statements do not allow for queries in queries" );
+ }
+ }
+}
diff --git a/dbaccess/qa/complex/dbaccess/RowSet.java b/dbaccess/qa/complex/dbaccess/RowSet.java
new file mode 100644
index 0000000000..ed42a271b4
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/RowSet.java
@@ -0,0 +1,942 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package complex.dbaccess;
+
+import com.sun.star.beans.UnknownPropertyException;
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.container.XIndexAccess;
+import com.sun.star.lang.WrappedTargetException;
+import com.sun.star.lang.XComponent;
+import com.sun.star.sdb.CommandType;
+import com.sun.star.sdb.XParametersSupplier;
+import com.sun.star.sdb.XResultSetAccess;
+import com.sun.star.sdb.XRowSetApproveBroadcaster;
+import com.sun.star.sdbc.SQLException;
+import com.sun.star.sdbc.XParameters;
+import com.sun.star.sdbc.XPreparedStatement;
+import com.sun.star.sdbc.XResultSet;
+import com.sun.star.sdbc.XResultSetUpdate;
+import com.sun.star.sdbc.XRow;
+import com.sun.star.sdbc.XRowSet;
+import com.sun.star.sdbc.XRowUpdate;
+import com.sun.star.sdbcx.XColumnsSupplier;
+import com.sun.star.sdbcx.XDeleteRows;
+import com.sun.star.sdbcx.XRowLocate;
+import com.sun.star.uno.UnoRuntime;
+
+import connectivity.tools.CRMDatabase;
+import connectivity.tools.DataSource;
+import connectivity.tools.HsqlDatabase;
+import connectivity.tools.sdb.Connection;
+import java.lang.reflect.Method;
+import java.util.Random;
+
+// ---------- junit imports -----------------
+import org.junit.After;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+
+public class RowSet extends TestCase
+{
+
+ static final int MAX_TABLE_ROWS = 100;
+ static final int MAX_FETCH_ROWS = 10;
+ private static final String NEXT = "next";
+ private static final String TEST21 = "Test21";
+ HsqlDatabase m_database;
+ DataSource m_dataSource;
+ XRowSet m_rowSet;
+ XResultSet m_resultSet;
+ XResultSetUpdate m_resultSetUpdate;
+ XRow m_row;
+ XRowLocate m_rowLocate;
+ XPropertySet m_rowSetProperties;
+ XParametersSupplier m_paramsSupplier;
+
+ private final Object failedResultSetMovementStressGuard = new Object();
+ private String failedResultSetMovementStressMessages = "";
+
+ private class ResultSetMovementStress implements Runnable
+ {
+
+ private final XResultSet m_resultSet;
+ private final XRow m_row;
+ private final int m_id;
+
+ private ResultSetMovementStress(XResultSet _resultSet, int _id) throws java.lang.Exception
+ {
+ m_resultSet = _resultSet;
+ m_row = UnoRuntime.queryInterface( XRow.class, m_resultSet );
+ m_id = _id;
+ }
+
+ public void run()
+ {
+ int i=-1;
+ try
+ {
+ m_resultSet.beforeFirst();
+ for (i = 0; m_resultSet.next(); ++i)
+ {
+ int pos = m_resultSet.getRow();
+ testPosition(m_resultSet, m_row, i + 1, "clone move(" + m_id + ")");
+ int pos2 = m_resultSet.getRow();
+ assertTrue("ResultSetMovementStress wrong position: " + i + " Pos1: " + pos + " Pos2: " + pos2, pos == pos2);
+ }
+ }
+ catch (Exception e)
+ {
+ synchronized (failedResultSetMovementStressGuard) {
+ failedResultSetMovementStressMessages
+ += "ResultSetMovementStress(" + m_id + ") failed at i="
+ + i + ": " + e + "\n";
+ }
+ }
+ }
+ }
+
+ private void createTestCase(boolean _defaultRowSet) throws Exception
+ {
+ if (m_database == null)
+ {
+ final CRMDatabase database = new CRMDatabase( getMSF(), false );
+ m_database = database.getDatabase();
+ m_dataSource = m_database.getDataSource();
+ }
+
+ createStructure();
+
+ if (_defaultRowSet)
+ {
+ createRowSet("TEST1", CommandType.TABLE, true, true);
+ }
+ }
+
+ @After public final void closeAndDeleteDatabase() {
+ if (m_database != null) {
+ m_database.closeAndDelete();
+ }
+ }
+
+ /** creates a com.sun.star.sdb.RowSet to use during the test
+ * @param command
+ * the command to use for the RowSet
+ * @param commandType
+ * the command type to use for the RowSet
+ * @param execute
+ * determines whether the RowSet should be executed
+ */
+ private void createRowSet(String command, int commandType, boolean execute) throws com.sun.star.uno.Exception
+ {
+ createRowSet(command, commandType, execute, false);
+ }
+
+
+ /** creates a com.sun.star.sdb.RowSet to use during the test
+ * @param command
+ * the command to use for the RowSet
+ * @param commandType
+ * the command type to use for the RowSet
+ * @param execute
+ * determines whether the RowSet should be executed
+ * @param limitFetchSize
+ * determines whether the fetch size of the RowSet should be limited to MAX_FETCH_ROWS
+ */
+ private void createRowSet(String command, int commandType, boolean execute, boolean limitFetchSize) throws com.sun.star.uno.Exception
+ {
+ m_rowSet = UnoRuntime.queryInterface( XRowSet.class, getMSF().createInstance( "com.sun.star.sdb.RowSet" ) );
+ final XPropertySet rowSetProperties = UnoRuntime.queryInterface( XPropertySet.class, m_rowSet );
+ rowSetProperties.setPropertyValue("Command", command);
+ rowSetProperties.setPropertyValue("CommandType", Integer.valueOf(commandType));
+ rowSetProperties.setPropertyValue("ActiveConnection", m_database.defaultConnection().getXConnection());
+ if (limitFetchSize)
+ {
+ rowSetProperties.setPropertyValue("FetchSize", Integer.valueOf(MAX_FETCH_ROWS));
+ }
+
+ m_resultSet = UnoRuntime.queryInterface( XResultSet.class, m_rowSet );
+ m_resultSetUpdate = UnoRuntime.queryInterface( XResultSetUpdate.class, m_rowSet );
+ m_row = UnoRuntime.queryInterface( XRow.class, m_rowSet );
+ m_rowLocate = UnoRuntime.queryInterface( XRowLocate.class, m_resultSet );
+ m_rowSetProperties = UnoRuntime.queryInterface( XPropertySet.class, m_rowSet );
+ m_paramsSupplier = UnoRuntime.queryInterface( XParametersSupplier.class, m_rowSet );
+
+ if (execute)
+ {
+ m_rowSet.execute();
+ }
+ }
+
+
+ @Test
+ public void testRowSet() throws java.lang.Exception
+ {
+
+ System.out.println("testing testRowSet");
+ createTestCase(true);
+
+ // sequential positioning
+ m_resultSet.beforeFirst();
+ testSequentialPositining(m_resultSet, m_row);
+
+ // absolute positioning
+ testAbsolutePositioning(m_resultSet, m_row);
+
+ // position during modify
+ testModifyPosition(m_resultSet, m_row);
+
+ // 3rd test
+ test3(createClone(), m_resultSet);
+ // 4th test
+ test4(m_resultSet);
+
+ // concurrent (multi threaded) access to the row set and its clones
+ testConcurrentAccess(m_resultSet);
+ }
+
+
+ XResultSet createClone() throws SQLException
+ {
+ final XResultSetAccess rowAcc = UnoRuntime.queryInterface( XResultSetAccess.class, m_rowSet );
+ return rowAcc.createResultSet();
+ }
+
+
+ void createStructure() throws SQLException
+ {
+ m_database.executeSQL("DROP TABLE \"TEST1\" IF EXISTS");
+ m_database.executeSQL("CREATE TABLE \"TEST1\" (\"ID\" integer not null primary key, \"col2\" varchar(50) )");
+
+ final Connection connection = m_database.defaultConnection();
+ final XPreparedStatement prep = connection.prepareStatement("INSERT INTO \"TEST1\" values (?,?)");
+ final XParameters para = UnoRuntime.queryInterface( XParameters.class, prep );
+ for (int i = 1; i <= MAX_TABLE_ROWS; ++i)
+ {
+ para.setInt(1, i);
+ para.setString(2, "Test" + i);
+ prep.executeUpdate();
+ }
+
+ connection.refreshTables();
+ }
+
+
+ void testPosition(XResultSet resultSet, XRow row, int expectedValue, String location) throws SQLException
+ {
+ final int val = row.getInt(1);
+ final int pos = resultSet.getRow();
+ assertTrue(location + ": value/position do not match: " + pos + " (pos) != " + val + " (val)", val == pos);
+ assertTrue(location + ": value/position are not as expected: " + val + " (val) != " + expectedValue + " (expected)", val == expectedValue);
+ }
+
+
+ void testSequentialPositining(XResultSet _resultSet, XRow _row) throws com.sun.star.uno.Exception
+ {
+ // 1st test
+ int i = 1;
+ while (_resultSet.next())
+ {
+ testPosition(_resultSet, _row, i, "testSequentialPositining");
+ ++i;
+ }
+ }
+
+
+ void testAbsolutePositioning(XResultSet _resultSet, XRow _row) throws com.sun.star.uno.Exception
+ {
+ for (int i = 1; i <= MAX_FETCH_ROWS; ++i)
+ {
+ final int calcPos = (MAX_TABLE_ROWS % i) + 1;
+ assertTrue("testAbsolutePositioning failed", _resultSet.absolute(calcPos));
+ testPosition(_resultSet, _row, calcPos, "testAbsolutePositioning");
+ }
+ }
+
+
+ void testModifyPosition(XResultSet _resultSet, XRow _row) throws com.sun.star.uno.Exception
+ {
+ final int testPos = 3;
+ assertTrue("testModifyPosition wants at least " + (testPos+1) + " rows", MAX_FETCH_ROWS >= testPos+1);
+ assertTrue("testModifyPosition failed on moving to row " + testPos, _resultSet.absolute(testPos));
+ UnoRuntime.queryInterface( XRowUpdate.class, _row ).updateString(2, TEST21);
+ testPosition(_resultSet, _row, testPos, "testModifyPosition");
+ UnoRuntime.queryInterface( XResultSetUpdate.class, _resultSet ).cancelRowUpdates();
+ }
+
+
+ void test3(XResultSet clone, XResultSet _resultSet) throws com.sun.star.uno.Exception
+ {
+ final XRow _row = UnoRuntime.queryInterface( XRow.class, _resultSet );
+ final XRow cloneRow = UnoRuntime.queryInterface( XRow.class, clone );
+ for (int i = 1; i <= MAX_FETCH_ROWS; ++i)
+ {
+ final int calcPos = (MAX_TABLE_ROWS % i) + 1;
+ if (clone.absolute(calcPos))
+ {
+ testPosition(clone, cloneRow, calcPos, "test3");
+ testAbsolutePositioning(_resultSet, _row);
+ testAbsolutePositioning(clone, cloneRow);
+ }
+ }
+ }
+
+
+ void test4(XResultSet _resultSet) throws com.sun.star.uno.Exception
+ {
+ final XRow _row = UnoRuntime.queryInterface( XRow.class, _resultSet );
+ _resultSet.beforeFirst();
+
+ for (int i = 1; i <= MAX_TABLE_ROWS; ++i)
+ {
+ _resultSet.next();
+ final XResultSet clone = createClone();
+ final XRow cloneRow = UnoRuntime.queryInterface( XRow.class, clone );
+ final int calcPos = MAX_TABLE_ROWS - 1;
+ if (calcPos != 0 && clone.absolute(calcPos))
+ {
+ testPosition(clone, cloneRow, calcPos, "test4: clone");
+ testPosition(_resultSet, _row, i, "test4: rowset");
+ }
+ }
+ }
+
+
+ void testConcurrentAccess(XResultSet _resultSet) throws Exception
+ {
+ System.out.println("testing Thread");
+
+ _resultSet.beforeFirst();
+
+ final int numberOfThreads = 10;
+
+ final Thread threads[] = new Thread[numberOfThreads];
+ for (int i = 0; i < numberOfThreads; ++i)
+ {
+ threads[i] = new Thread(new ResultSetMovementStress(createClone(), i));
+ System.out.println("starting thread " + (i + 1) + " of " + (numberOfThreads));
+ threads[i].start();
+ }
+
+ for (int i = 0; i < numberOfThreads; ++i)
+ {
+ threads[i].join();
+ }
+ synchronized (failedResultSetMovementStressGuard) {
+ assertEquals("", failedResultSetMovementStressMessages);
+ }
+ }
+
+
+ @Test
+ public void testRowSetEvents() throws java.lang.Exception
+ {
+ System.out.println("testing RowSet Events");
+ createTestCase(true);
+
+ // first we create our RowSet object
+ final RowSetEventListener pRow = new RowSetEventListener();
+
+ final XColumnsSupplier colSup = UnoRuntime.queryInterface( XColumnsSupplier.class, m_rowSet );
+ final XPropertySet col = UnoRuntime.queryInterface( XPropertySet.class, colSup.getColumns().getByName( "ID" ) );
+ col.addPropertyChangeListener("Value", pRow);
+ m_rowSetProperties.addPropertyChangeListener("IsModified", pRow);
+ m_rowSetProperties.addPropertyChangeListener("IsNew", pRow);
+ m_rowSetProperties.addPropertyChangeListener("IsRowCountFinal", pRow);
+ m_rowSetProperties.addPropertyChangeListener("RowCount", pRow);
+
+ final XRowSetApproveBroadcaster xApBroad = UnoRuntime.queryInterface( XRowSetApproveBroadcaster.class, m_resultSet );
+ xApBroad.addRowSetApproveListener(pRow);
+ m_rowSet.addRowSetListener(pRow);
+
+ // do some movements to check if we got all notifications
+ final Class<?> cResSet = Class.forName("com.sun.star.sdbc.XResultSet");
+ final boolean moves[] = new boolean[9];
+ for (int i = 0; i < moves.length; ++i)
+ {
+ moves[i] = false;
+ }
+ moves[RowSetEventListener.APPROVE_CURSOR_MOVE] = true;
+ moves[RowSetEventListener.COLUMN_VALUE] = true;
+ moves[RowSetEventListener.CURSOR_MOVED] = true;
+ moves[RowSetEventListener.IS_ROW_COUNT_FINAL] = true;
+ moves[RowSetEventListener.ROW_COUNT] = true;
+
+ testCursorMove(m_resultSet, cResSet.getMethod("afterLast", (Class[]) null), pRow, moves, null);
+
+ moves[RowSetEventListener.IS_ROW_COUNT_FINAL] = false;
+ moves[RowSetEventListener.ROW_COUNT] = false;
+ testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null);
+ testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null);
+ testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null);
+ testCursorMove(m_resultSet, cResSet.getMethod("last", (Class[]) null), pRow, moves, null);
+ testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null);
+ testCursorMove(m_resultSet, cResSet.getMethod("first", (Class[]) null), pRow, moves, null);
+ testCursorMove(m_resultSet, cResSet.getMethod("previous", (Class[]) null), pRow, moves, null);
+ testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null);
+ moves[RowSetEventListener.IS_MODIFIED] = true;
+ final XRowUpdate updRow = UnoRuntime.queryInterface( XRowUpdate.class, m_resultSet );
+ updRow.updateString(2, TEST21);
+ testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null);
+
+ moves[RowSetEventListener.IS_MODIFIED] = false;
+ updRow.updateString(2, m_row.getString(2));
+ testCursorMove(m_resultSet, cResSet.getMethod(NEXT, (Class[]) null), pRow, moves, null);
+
+ moves[RowSetEventListener.IS_MODIFIED] = false;
+ final Class<?> cupd = Class.forName("com.sun.star.sdbc.XResultSetUpdate");
+ final XResultSetUpdate upd = UnoRuntime.queryInterface( XResultSetUpdate.class, m_resultSet );
+ testCursorMove(upd, cupd.getMethod("moveToInsertRow", (Class[]) null), pRow, moves, null);
+
+ updRow.updateInt(1, MAX_TABLE_ROWS + 2);
+ updRow.updateString(2, "HHHH");
+ moves[RowSetEventListener.APPROVE_CURSOR_MOVE] = false;
+ moves[RowSetEventListener.CURSOR_MOVED] = false;
+ moves[RowSetEventListener.IS_MODIFIED] = true;
+ moves[RowSetEventListener.IS_NEW] = true;
+ moves[RowSetEventListener.ROW_COUNT] = true;
+ moves[RowSetEventListener.APPROVE_ROW_CHANGE] = true;
+ moves[RowSetEventListener.ROW_CHANGED] = true;
+ testCursorMove(upd, cupd.getMethod("insertRow", (Class[]) null), pRow, moves, null);
+
+ moves[RowSetEventListener.IS_NEW] = false;
+ moves[RowSetEventListener.ROW_COUNT] = false;
+ m_resultSet.first();
+ updRow.updateInt(1, MAX_TABLE_ROWS + 3);
+ updRow.updateString(2, "__");
+ testCursorMove(upd, cupd.getMethod("updateRow", (Class[]) null), pRow, moves, null);
+
+ moves[RowSetEventListener.IS_NEW] = true;
+ moves[RowSetEventListener.ROW_COUNT] = true;
+ m_resultSet.first();
+ testCursorMove(upd, cupd.getMethod("deleteRow", (Class[]) null), pRow, moves, null);
+
+ moves[RowSetEventListener.IS_NEW] = false;
+ moves[RowSetEventListener.COLUMN_VALUE] = true;
+ moves[RowSetEventListener.ROW_COUNT] = false;
+ m_resultSet.first();
+ updRow.updateString(2, TEST21);
+ testCursorMove(m_resultSet, cResSet.getMethod("refreshRow", (Class[]) null), pRow, moves, null);
+
+ m_resultSet.first();
+ updRow.updateString(2, TEST21);
+ testCursorMove(upd, cupd.getMethod("cancelRowUpdates", (Class[]) null), pRow, moves, null);
+
+ for (int i = 0; i < moves.length; ++i)
+ {
+ moves[i] = false;
+ }
+ moves[RowSetEventListener.APPROVE_CURSOR_MOVE] = true;
+ moves[RowSetEventListener.COLUMN_VALUE] = true;
+ moves[RowSetEventListener.CURSOR_MOVED] = true;
+
+ final Class<?> cloc = Class.forName("com.sun.star.sdbcx.XRowLocate");
+ m_resultSet.first();
+ final Object bookmark = m_rowLocate.getBookmark();
+ m_resultSet.next();
+ final Object temp[] = new Object[1];
+ temp[0] = bookmark;
+ Class ctemp[] = new Class[1];
+ ctemp[0] = Object.class;
+ testCursorMove(m_rowLocate, cloc.getMethod("moveToBookmark", ctemp), pRow, moves, temp);
+
+ final Object temp2[] = new Object[2];
+ temp2[0] = bookmark;
+ temp2[1] = Integer.valueOf(1);
+ final Class ctemp2[] = new Class[2];
+ ctemp2[0] = Object.class;
+ ctemp2[1] = int.class;
+ testCursorMove(m_rowLocate, cloc.getMethod("moveRelativeToBookmark", ctemp2), pRow, moves, temp2);
+
+ for (int i = 0; i < moves.length; ++i)
+ {
+ moves[i] = false;
+ }
+ moves[RowSetEventListener.APPROVE_ROW_CHANGE] = true;
+ moves[RowSetEventListener.ROW_CHANGED] = true;
+ moves[RowSetEventListener.ROW_COUNT] = true;
+ final Class<?> cdelRows = Class.forName("com.sun.star.sdbcx.XDeleteRows");
+ ctemp[0] = Object[].class;
+ final XDeleteRows delRows = UnoRuntime.queryInterface( XDeleteRows.class, m_resultSet );
+ final Object bookmarks[] = new Object[5];
+ m_resultSet.first();
+ for (int i = 0; i < bookmarks.length; ++i)
+ {
+ m_resultSet.next();
+ bookmarks[i] = m_rowLocate.getBookmark();
+ }
+
+ temp[0] = bookmarks;
+ testCursorMove(delRows, cdelRows.getMethod("deleteRows", ctemp), pRow, moves, temp);
+
+ // now destroy the RowSet
+ final XComponent xComp = UnoRuntime.queryInterface( XComponent.class, m_resultSet );
+ xComp.dispose();
+ }
+
+
+ private void testCursorMove(Object res, Method _method, RowSetEventListener _evt, boolean _must[], Object args[]) throws java.lang.Exception
+ {
+ _evt.clearCalling();
+ _method.invoke(res, args);
+
+ System.out.println("testing events for " + _method.getName());
+ final int calling[] = _evt.getCalling();
+ int pos = 1;
+ assertTrue("Callings are not in the correct order for APPROVE_CURSOR_MOVE ",
+ (!_must[RowSetEventListener.APPROVE_CURSOR_MOVE] || calling[RowSetEventListener.APPROVE_CURSOR_MOVE] == -1) || calling[RowSetEventListener.APPROVE_CURSOR_MOVE] == pos++);
+ assertTrue("Callings are not in the correct order for APPROVE_ROW_CHANGE",
+ (!_must[RowSetEventListener.APPROVE_ROW_CHANGE] || calling[RowSetEventListener.APPROVE_ROW_CHANGE] == -1) || calling[RowSetEventListener.APPROVE_ROW_CHANGE] == pos++);
+ assertTrue("Callings are not in the correct order for COLUMN_VALUE",
+ (!_must[RowSetEventListener.COLUMN_VALUE] || calling[RowSetEventListener.COLUMN_VALUE] == -1) || calling[RowSetEventListener.COLUMN_VALUE] == pos++);
+ assertTrue("Callings are not in the correct order for CURSOR_MOVED",
+ (!_must[RowSetEventListener.CURSOR_MOVED] || calling[RowSetEventListener.CURSOR_MOVED] == -1) || calling[RowSetEventListener.CURSOR_MOVED] == pos++);
+ assertTrue("Callings are not in the correct order for ROW_CHANGED",
+ (!_must[RowSetEventListener.ROW_CHANGED] || calling[RowSetEventListener.ROW_CHANGED] == -1) || calling[RowSetEventListener.ROW_CHANGED] == pos++);
+ assertTrue("Callings are not in the correct order for IS_MODIFIED",
+ (!_must[RowSetEventListener.IS_MODIFIED] || calling[RowSetEventListener.IS_MODIFIED] == -1) || calling[RowSetEventListener.IS_MODIFIED] == pos++);
+ assertTrue("Callings are not in the correct order for IS_NEW",
+ (!_must[RowSetEventListener.IS_NEW] || calling[RowSetEventListener.IS_NEW] == -1) || calling[RowSetEventListener.IS_NEW] == pos++);
+ assertTrue("Callings are not in the correct order for ROW_COUNT",
+ (!_must[RowSetEventListener.ROW_COUNT] || calling[RowSetEventListener.ROW_COUNT] == -1) || calling[RowSetEventListener.ROW_COUNT] == pos++);
+ assertTrue("Callings are not in the correct order for IS_ROW_COUNT_FINAL",
+ (!_must[RowSetEventListener.IS_ROW_COUNT_FINAL] || calling[RowSetEventListener.IS_ROW_COUNT_FINAL] == -1) || calling[RowSetEventListener.IS_ROW_COUNT_FINAL] == pos);
+
+ _evt.clearCalling();
+ }
+
+
+ /** returns the current row count of the RowSet
+ */
+ private int currentRowCount() throws UnknownPropertyException, WrappedTargetException
+ {
+ final Integer rowCount = (Integer) m_rowSetProperties.getPropertyValue("RowCount");
+ return rowCount.intValue();
+ }
+
+
+ /** positions the row set at an arbitrary position between 2 and (current row count - 1)
+ */
+ private int positionRandom() throws SQLException, UnknownPropertyException, WrappedTargetException
+ {
+ // note: obviously this should subtract 2 but actually subtract 3
+ // because if we have just deleted the current row then
+ // ORowSetBase::impl_getRowCount() will lie and currentRowCount()
+ // returns 1 more than the actual number of rows and then
+ // positionRandom() followed by deleteRow() deletes *last* row
+ final int position = (new Random()).nextInt(currentRowCount() - 3) + 2;
+ assertTrue("sub task failed: could not position to row no. " + (Integer.valueOf(position)).toString(),
+ m_resultSet.absolute(position));
+ return m_resultSet.getRow();
+ }
+
+
+ /** moves the result set to a random record between 2 and (current row count - 1), and deletes this record
+ *
+ * After returning from this method, the row set is still positioned at the deleted record
+ * @return
+ * the number/position of the record which has been deleted
+ */
+ private int deleteRandom() throws SQLException, UnknownPropertyException, WrappedTargetException
+ {
+ // check if the current position and the row count in the result set is changed by a deletion (it should not)
+ final int positionBefore = positionRandom();
+ final int rowCountBefore = currentRowCount();
+
+ m_resultSetUpdate.deleteRow();
+
+ final int positionAfter = m_resultSet.getRow();
+ final int rowCountAfter = currentRowCount();
+ assertTrue("position changed during |deleteRow| (it should not)", positionAfter == positionBefore);
+ assertTrue("row count changed with a |deleteRow| (it should not)", rowCountBefore == rowCountAfter);
+ assertTrue("RowSet does not report the current row as deleted after |deleteRow|", m_resultSet.rowDeleted());
+
+ return positionBefore;
+ }
+
+
+ @Test
+ public void testDeleteBehavior() throws Exception
+ {
+ createTestCase(true);
+
+ // ensure that all records are known
+ m_resultSet.last();
+ final int initialRowCount = currentRowCount();
+
+ // delete a random row
+ int deletedRow = deleteRandom();
+
+
+ // asking for the bookmark of a deleted row should fail
+ boolean caughtException = false;
+ try
+ {
+ m_rowLocate.getBookmark();
+ }
+ catch (SQLException e)
+ {
+ caughtException = true;
+ }
+ assertTrue("asking for the bookmark of a deleted row should throw an exception", caughtException);
+
+
+ // isXXX methods should return |false| on a deleted row
+ assertTrue("one of the isFoo failed after |deleteRow|", !m_resultSet.isBeforeFirst() && !m_resultSet.isAfterLast() && !m_resultSet.isFirst() && !m_resultSet.isLast());
+ // note that we can assume that isFirst / isLast also return |false|, since deleteRandom did
+ // not position on the first or last record, but inbetween
+
+
+ // check if moving away from this row in either direction yields the expected results
+ assertTrue("|previous| after |deleteRow| failed", m_resultSet.previous());
+ final int positionPrevious = m_resultSet.getRow();
+ assertTrue("position after |previous| after |deleteRow| is not as expected", positionPrevious == deletedRow - 1);
+
+ deletedRow = deleteRandom();
+ assertTrue("|next| after |deleteRow| failed", m_resultSet.next());
+ final int positionAfter = m_resultSet.getRow();
+ assertTrue("position after |next| after |deleteRow| is not as expected", positionAfter == deletedRow);
+ // since the deleted record "vanishes" as soon as the cursor is moved away from it, the absolute position does
+ // not change with a |next| call here
+
+
+ // check if the deleted rows really vanished after moving away from them
+ assertTrue("row count did not change as expected after two deletions", initialRowCount - 2 == currentRowCount());
+
+
+ // check if the deleted row vanishes after moving to the insertion row
+ final int rowCountBefore = currentRowCount();
+ final int deletedPos = deleteRandom();
+ m_resultSetUpdate.moveToInsertRow();
+ assertTrue("moving to the insertion row immediately after |deleteRow| does not adjust the row count", rowCountBefore == currentRowCount() + 1);
+
+ m_resultSetUpdate.moveToCurrentRow();
+ assertTrue("|moveToCurrentRow| after |deleteRow| + |moveToInsertRow| results in unexpected position",
+ (m_resultSet.getRow() == deletedPos) && !m_resultSet.rowDeleted());
+
+ // the same, but this time with deleting the first row (which is not covered by deleteRandom)
+ m_resultSet.last();
+ m_resultSetUpdate.deleteRow();
+ m_resultSetUpdate.moveToInsertRow();
+ m_resultSetUpdate.moveToCurrentRow();
+ assertTrue("|last| + |deleteRow| + |moveToInsertRow| + |moveToCurrentRow| results in wrong state", m_resultSet.isAfterLast());
+
+
+ // check if deleting a deleted row fails as expected
+ deleteRandom();
+ caughtException = false;
+ try
+ {
+ m_resultSetUpdate.deleteRow();
+ }
+ catch (SQLException e)
+ {
+ caughtException = true;
+ }
+ assertTrue("deleting a deleted row succeeded - it shouldn't", caughtException);
+
+
+ // check if deleteRows fails if it contains the bookmark of a previously-deleted row
+ m_resultSet.first();
+ final Object firstBookmark = m_rowLocate.getBookmark();
+ positionRandom();
+ final Object deleteBookmark = m_rowLocate.getBookmark();
+ m_resultSetUpdate.deleteRow();
+ final XDeleteRows multiDelete = UnoRuntime.queryInterface( XDeleteRows.class, m_resultSet );
+ final int[] deleteSuccess = multiDelete.deleteRows(new Object[]
+ {
+ firstBookmark, deleteBookmark
+ });
+ assertTrue("XDeleteRows::deleteRows with the bookmark of an already-deleted row failed",
+ (deleteSuccess.length == 2) && (deleteSuccess[0] != 0) && (deleteSuccess[1] == 0));
+
+
+ // check if refreshing a deleted row fails as expected
+ deleteRandom();
+ caughtException = false;
+ try
+ {
+ m_resultSet.refreshRow();
+ }
+ catch (SQLException e)
+ {
+ caughtException = true;
+ }
+ assertTrue("refreshing a deleted row succeeded - it shouldn't", caughtException);
+
+
+ // rowUpdated/rowDeleted
+ deleteRandom();
+ assertTrue("rowDeleted and/or rowUpdated are wrong on a deleted row", !m_resultSet.rowUpdated() && !m_resultSet.rowInserted());
+
+
+ // updating values in a deleted row should fail
+ deleteRandom();
+ final XRowUpdate rowUpdated = UnoRuntime.queryInterface( XRowUpdate.class, m_resultSet );
+ caughtException = false;
+ try
+ {
+ rowUpdated.updateString(2, TEST21);
+ }
+ catch (SQLException e)
+ {
+ caughtException = true;
+ }
+ assertTrue("updating values in a deleted row should not succeed", caughtException);
+ }
+
+
+ /** checks whether deletions on the main RowSet properly interfere (or don't interfere) with the movement
+ * on a clone of the RowSet
+ */
+ @Test
+ public void testCloneMovesPlusDeletions() throws Exception
+ {
+ createTestCase(true);
+ // ensure that all records are known
+ m_resultSet.last();
+
+ final XResultSet clone = createClone();
+ final XRowLocate cloneRowLocate = UnoRuntime.queryInterface( XRowLocate.class, clone );
+
+ positionRandom();
+
+
+ // move the clone to the same record as the RowSet, and delete this record
+ cloneRowLocate.moveToBookmark(m_rowLocate.getBookmark());
+ final int clonePosition = clone.getRow();
+ m_resultSetUpdate.deleteRow();
+
+ assertTrue("clone doesn't know that its current row has been deleted via the RowSet", clone.rowDeleted());
+ assertTrue("clone's position changed somehow during deletion", clonePosition == clone.getRow());
+
+
+ // move the row set away from the deleted record. This should still not touch the state of the clone
+ m_resultSet.previous();
+
+ assertTrue("clone doesn't know (anymore) that its current row has been deleted via the RowSet", clone.rowDeleted());
+ assertTrue("clone's position changed somehow during deletion and RowSet-movement", clonePosition == clone.getRow());
+
+
+ // move the clone away from the deleted record
+ clone.next();
+ assertTrue("clone still assumes that its row is deleted - but we already moved it", !clone.rowDeleted());
+
+
+ // check whether deleting the extremes (first / last) work
+ m_resultSet.first();
+ cloneRowLocate.moveToBookmark(m_rowLocate.getBookmark());
+ m_resultSetUpdate.deleteRow();
+ clone.previous();
+ assertTrue("deleting the first record left the clone in a strange state (after |previous|)", clone.isBeforeFirst());
+ clone.next();
+ assertTrue("deleting the first record left the clone in a strange state (after |previous| + |next|)", clone.isFirst());
+
+ m_resultSet.last();
+ cloneRowLocate.moveToBookmark(m_rowLocate.getBookmark());
+ m_resultSetUpdate.deleteRow();
+ clone.next();
+ assertTrue("deleting the last record left the clone in a strange state (after |next|)", clone.isAfterLast());
+ clone.previous();
+ assertTrue("deleting the first record left the clone in a strange state (after |next| + |previous|)", clone.isLast());
+
+
+ // check whether movements of the clone interfere with movements of the RowSet, if the latter is on a deleted row
+ final int positionBefore = positionRandom();
+ m_resultSetUpdate.deleteRow();
+ assertTrue("|deleteRow|, but no |rowDeleted| (this should have been found much earlier!)", m_resultSet.rowDeleted());
+ clone.beforeFirst();
+ while (clone.next()) {}
+ assertTrue("row set forgot that the current row is deleted", m_resultSet.rowDeleted());
+
+ assertTrue("moving to the next record after |deleteRow| and clone moves failed", m_resultSet.next());
+ assertTrue("wrong position after |deleteRow| and clone movement", !m_resultSet.isAfterLast() && !m_resultSet.isBeforeFirst());
+ assertTrue("wrong absolute position after |deleteRow| and clone movement", m_resultSet.getRow() == positionBefore);
+ }
+
+
+ /** checks whether insertions on the main RowSet properly interfere (or don't interfere) with the movement
+ * on a clone of the RowSet
+ */
+ @Test
+ public void testCloneMovesPlusInsertions() throws Exception
+ {
+ createTestCase(true);
+ // ensure that all records are known
+ m_rowSetProperties.setPropertyValue("FetchSize", Integer.valueOf(10));
+
+ final XResultSet clone = createClone();
+ final XRow cloneRow = UnoRuntime.queryInterface( XRow.class, clone );
+
+
+ // first check the basic scenario without the |moveToInsertRow| |moveToCurrentRow|, to ensure that
+ // really those are broken, if at all
+ m_resultSet.last();
+ clone.first();
+ clone.absolute(11);
+ clone.first();
+
+ final int rowValue1 = m_row.getInt(1);
+ final int rowPos = m_resultSet.getRow();
+ final int rowValue2 = m_row.getInt(1);
+ assertTrue("repeated query for the same column value delivers different values (" + rowValue1 + " and " + rowValue2 + ") on row: " + rowPos,
+ rowValue1 == rowValue2);
+
+ testPosition(clone, cloneRow, 1, "mixed clone/rowset move: clone check");
+ testPosition(m_resultSet, m_row, MAX_TABLE_ROWS, "mixed clone/rowset move: rowset check");
+
+
+ // now the complete scenario
+ m_resultSet.last();
+ m_resultSetUpdate.moveToInsertRow();
+ clone.first();
+ clone.absolute(11);
+ clone.first();
+ m_resultSetUpdate.moveToCurrentRow();
+
+ testPosition(clone, cloneRow, 1, "mixed clone/rowset move/insertion: clone check");
+ testPosition(m_resultSet, m_row, 100, "mixed clone/rowset move/insertion: rowset check");
+ }
+
+
+ private void testTableParameters() throws com.sun.star.uno.Exception
+ {
+ // for a row set simply based on a table, there should be not parameters at all
+ createRowSet("products", CommandType.TABLE, false);
+ verifyParameters(new String[]
+ {
+ }, "testTableParameters");
+ }
+
+
+ private void testParametersAfterNormalExecute() throws com.sun.star.uno.Exception
+ {
+ createRowSet("SELECT * FROM \"customers\"", CommandType.COMMAND, true);
+ m_rowSetProperties.setPropertyValue("Command", "SELECT * FROM \"customers\" WHERE \"City\" = :city");
+ final XParameters rowsetParams = UnoRuntime.queryInterface( XParameters.class, m_rowSet );
+ rowsetParams.setString(1, "London");
+ m_rowSet.execute();
+ }
+
+
+ private void verifyParameters(String[] _paramNames, String _context) throws com.sun.star.uno.Exception
+ {
+ final XIndexAccess params = m_paramsSupplier.getParameters();
+ final int expected = _paramNames.length;
+ final int found = params != null ? params.getCount() : 0;
+
+ assertTrue("wrong number of parameters (expected: " + expected + ", found: " + found + ") in " + _context,
+ found == expected);
+
+ if (found == 0)
+ {
+ return;
+ }
+
+ for (int i = 0; i < expected; ++i)
+ {
+ final XPropertySet parameter = UnoRuntime.queryInterface( XPropertySet.class, params.getByIndex( i ) );
+
+ final String expectedName = _paramNames[i];
+ final String foundName = (String) parameter.getPropertyValue("Name");
+ assertTrue("wrong parameter name (expected: " + expectedName + ", found: " + foundName + ") in" + _context,
+ expectedName.equals(foundName));
+ }
+ }
+
+
+ private void testParametrizedQuery() throws com.sun.star.uno.Exception
+ {
+ // for a row set based on a parametrized query, those parameters should be properly
+ // recognized
+ m_dataSource.createQuery("products like", "SELECT * FROM \"products\" WHERE \"Name\" LIKE :product_name");
+ createRowSet("products like", CommandType.QUERY, false);
+ verifyParameters(new String[]
+ {
+ "product_name"
+ }, "testParametrizedQuery");
+ }
+
+
+ private void testParametersInteraction() throws com.sun.star.uno.Exception
+ {
+ createRowSet("products like", CommandType.QUERY, false);
+
+ // let's fill in a parameter value via XParameters, and see whether it is respected by the parameters container
+ final XParameters rowsetParams = UnoRuntime.queryInterface(XParameters.class, m_rowSet);
+ rowsetParams.setString(1, "Apples");
+
+ XIndexAccess params = m_paramsSupplier.getParameters();
+ XPropertySet firstParam = UnoRuntime.queryInterface( XPropertySet.class, params.getByIndex( 0 ) );
+ Object firstParamValue = firstParam.getPropertyValue("Value");
+
+ assertTrue("XParameters and the parameters container do not properly interact",
+ "Apples".equals(firstParamValue));
+
+ // let's see whether this also survives an execute of the row set
+ rowsetParams.setString(1, "Oranges");
+ m_rowSet.execute();
+ {
+ // TODO: the following would not be necessary if the parameters container would *survive*
+ // the execution of the row set. It currently doesn't (though the values it represents do).
+ // It would be nice, but not strictly necessary, if it would.
+ params = m_paramsSupplier.getParameters();
+ firstParam = UnoRuntime.queryInterface( XPropertySet.class, params.getByIndex( 0 ) );
+ }
+ firstParamValue = firstParam.getPropertyValue("Value");
+ assertTrue("XParameters and the parameters container do not properly interact, after the row set has been executed",
+ "Oranges".equals(firstParamValue));
+ }
+
+
+ private void testParametersInFilter() throws com.sun.star.uno.Exception
+ {
+ createRowSet("SELECT * FROM \"customers\"", CommandType.COMMAND, false);
+ m_rowSetProperties.setPropertyValue("Filter", "\"City\" = :city");
+
+ m_rowSetProperties.setPropertyValue("ApplyFilter", Boolean.TRUE);
+ verifyParameters(new String[]
+ {
+ "city"
+ }, "testParametersInFilter");
+
+ m_rowSetProperties.setPropertyValue("ApplyFilter", Boolean.FALSE);
+ verifyParameters(new String[]
+ {
+ }, "testParametersInFilter");
+ }
+
+
+ /** checks the XParametersSupplier functionality of a RowSet
+ */
+ @Test
+ public void testParameters() throws Exception
+ {
+ createTestCase(false);
+ // use an own RowSet instance, not the one which is also used for the other cases
+
+ testTableParameters();
+ testParametrizedQuery();
+ testParametersInFilter();
+
+ testParametersAfterNormalExecute();
+
+ testParametersInteraction();
+ }
+}
+
diff --git a/dbaccess/qa/complex/dbaccess/RowSetEventListener.java b/dbaccess/qa/complex/dbaccess/RowSetEventListener.java
new file mode 100644
index 0000000000..991dfb7af7
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/RowSetEventListener.java
@@ -0,0 +1,102 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+package complex.dbaccess;
+
+import com.sun.star.sdb.XRowSetApproveListener;
+import com.sun.star.sdbc.XRowSetListener;
+import com.sun.star.sdb.RowChangeEvent;
+import com.sun.star.lang.EventObject;
+import com.sun.star.beans.XPropertyChangeListener;
+
+public final class RowSetEventListener implements XRowSetApproveListener,XRowSetListener,XPropertyChangeListener
+{
+ public static final int APPROVE_CURSOR_MOVE = 0;
+ public static final int APPROVE_ROW_CHANGE = 1;
+ public static final int COLUMN_VALUE = 2;
+ public static final int CURSOR_MOVED = 3;
+ public static final int ROW_CHANGED = 4;
+ public static final int IS_MODIFIED = 5;
+ public static final int IS_NEW = 6;
+ public static final int ROW_COUNT = 7;
+ public static final int IS_ROW_COUNT_FINAL = 8;
+
+ private int callPos = 1;
+ private int calling [];
+
+ RowSetEventListener(){
+ calling = new int [9];
+ clearCalling();
+ }
+ public int[] getCalling(){
+ return calling;
+ }
+ public void clearCalling(){
+ for(int i = 0 ; i < calling.length; ++i){
+ calling[i] = -1;
+ }
+ callPos = 1;
+ }
+ // XEventListener
+ public void disposing(com.sun.star.lang.EventObject event)
+ {
+ }
+ // XRowSetApproveBroadcaster
+ public boolean approveCursorMove(EventObject event)
+ {
+ calling[APPROVE_CURSOR_MOVE] = callPos++;
+ return true;
+ }
+ public boolean approveRowChange(RowChangeEvent event)
+ {
+ calling[APPROVE_ROW_CHANGE] = callPos++;
+ return true;
+ }
+ public boolean approveRowSetChange(EventObject event)
+ {
+ return true;
+ }
+
+ // XRowSetListener
+ public void cursorMoved(com.sun.star.lang.EventObject event)
+ {
+ calling[CURSOR_MOVED] = callPos++;
+ }
+ public void rowChanged(com.sun.star.lang.EventObject event)
+ {
+ calling[ROW_CHANGED] = callPos++;
+ }
+ public void rowSetChanged(com.sun.star.lang.EventObject event)
+ {
+ }
+
+ public void propertyChange(com.sun.star.beans.PropertyChangeEvent propertyChangeEvent) {
+ if ( "Value".equals(propertyChangeEvent.PropertyName) ){
+ calling[COLUMN_VALUE] = callPos++;
+ } else if ( "IsModified".equals(propertyChangeEvent.PropertyName) ){
+ calling[IS_MODIFIED] = callPos++;
+ } else if ( "IsNew".equals(propertyChangeEvent.PropertyName) ){
+ calling[IS_NEW] = callPos++;
+ } else if ( "RowCount".equals(propertyChangeEvent.PropertyName) ){
+ calling[ROW_COUNT] = callPos++;
+ } else if ( "IsRowCountFinal".equals(propertyChangeEvent.PropertyName) ){
+ calling[IS_ROW_COUNT_FINAL] = callPos++;
+ }
+ }
+
+}
diff --git a/dbaccess/qa/complex/dbaccess/SingleSelectQueryComposer.java b/dbaccess/qa/complex/dbaccess/SingleSelectQueryComposer.java
new file mode 100644
index 0000000000..656b44d00c
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/SingleSelectQueryComposer.java
@@ -0,0 +1,351 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package complex.dbaccess;
+
+import com.sun.star.beans.PropertyState;
+import com.sun.star.sdb.SQLFilterOperator;
+import com.sun.star.beans.PropertyAttribute;
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.beans.XPropertyContainer;
+import com.sun.star.beans.NamedValue;
+import com.sun.star.container.XNameAccess;
+import com.sun.star.sdbcx.XTablesSupplier;
+import com.sun.star.sdb.XParametersSupplier;
+import com.sun.star.beans.PropertyValue;
+import com.sun.star.sdbcx.XColumnsSupplier;
+import com.sun.star.container.XIndexAccess;
+import com.sun.star.sdb.CommandType;
+import com.sun.star.sdb.XSingleSelectQueryComposer;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.sdbc.DataType;
+import com.sun.star.sdbc.SQLException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+
+// ---------- junit imports -----------------
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+public class SingleSelectQueryComposer extends CRMBasedTestCase
+{
+
+ private XSingleSelectQueryComposer m_composer = null;
+ private static final String COMPLEXFILTER = "( \"ID\" = 1 AND \"Postal\" = '4' )"
+ + " OR ( \"ID\" = 2 AND \"Postal\" = '5' )"
+ + " OR ( \"ID\" = 3 AND \"Postal\" = '6' AND \"Address\" = '7' )"
+ + " OR ( \"Address\" = '8' )"
+ + " OR ( \"Postal\" = '9' )"
+ + " OR ( NOW( ) = {d '2010-01-01' } )";
+ private static final String INNERPRODUCTSQUERY = "products (inner)";
+
+
+ private void createQueries() throws Exception
+ {
+ m_database.getDatabase().getDataSource().createQuery(INNERPRODUCTSQUERY, "SELECT * FROM \"products\"");
+ }
+
+
+ @Override
+ protected void createTestCase() throws Exception
+ {
+ super.createTestCase();
+
+ createQueries();
+
+ m_composer = createQueryComposer();
+ }
+
+
+ private void checkAttributeAccess(String _attributeName, String _attributeValue)
+ {
+ System.out.println("setting " + _attributeName + " to " + _attributeValue);
+ String realValue = null;
+ try
+ {
+ final Class<?> composerClass = m_composer.getClass();
+ final Method attributeGetter = composerClass.getMethod("get" + _attributeName, new Class[]
+ {
+ });
+ final Method attributeSetter = composerClass.getMethod("set" + _attributeName, new Class[]
+ {
+ String.class
+ });
+
+ attributeSetter.invoke(m_composer, new Object[]
+ {
+ _attributeValue
+ });
+ realValue = (String) attributeGetter.invoke(m_composer, new Object[]
+ {
+ });
+ }
+ catch (NoSuchMethodException e)
+ {
+ }
+ catch (IllegalAccessException e)
+ {
+ }
+ catch (InvocationTargetException e)
+ {
+ }
+ assertTrue("set/get" + _attributeName + " not working as expected (set: " + _attributeValue + ", get: " + (realValue != null ? realValue : "null") + ")",
+ realValue.equals(_attributeValue));
+ System.out.println(" (results in " + m_composer.getQuery() + ")");
+ }
+
+ /** tests setCommand of the composer
+ */
+ @Test
+ public void testSetCommand() throws Exception
+ {
+ System.out.println("testing SingleSelectQueryComposer's setCommand");
+
+ final String table = "SELECT * FROM \"customers\"";
+ m_composer.setCommand("customers", CommandType.TABLE);
+ assertTrue("setCommand/getQuery TABLE inconsistent", m_composer.getQuery().equals(table));
+
+ m_database.getDatabase().getDataSource().createQuery("set command test", "SELECT * FROM \"orders for customer\" \"a\", \"customers\" \"b\" WHERE \"a\".\"Product Name\" = \"b\".\"Name\"");
+ m_composer.setCommand("set command test", CommandType.QUERY);
+ assertTrue("setCommand/getQuery QUERY inconsistent", m_composer.getQuery().equals(m_database.getDatabase().getDataSource().getQueryDefinition("set command test").getCommand()));
+
+ final String sql = "SELECT * FROM \"orders for customer\" WHERE \"Product Name\" = 'test'";
+ m_composer.setCommand(sql, CommandType.COMMAND);
+ assertTrue("setCommand/getQuery COMMAND inconsistent", m_composer.getQuery().equals(sql));
+ }
+
+ /** tests accessing attributes of the composer (order, filter, group by, having)
+ */
+ @Test
+ public void testAttributes() throws Exception
+ {
+ System.out.println("testing SingleSelectQueryComposer's attributes (order, filter, group by, having)");
+
+ System.out.println("check setElementaryQuery");
+
+ final String simpleQuery2 = "SELECT * FROM \"customers\" WHERE \"Name\" = 'oranges'";
+ m_composer.setElementaryQuery(simpleQuery2);
+ assertTrue("setElementaryQuery/getQuery inconsistent", m_composer.getQuery().equals(simpleQuery2));
+
+ System.out.println("check setQuery");
+ final String simpleQuery = "SELECT * FROM \"customers\"";
+ m_composer.setQuery(simpleQuery);
+ assertTrue("set/getQuery inconsistent", m_composer.getQuery().equals(simpleQuery));
+
+ checkAttributeAccess("Filter", "\"Name\" = 'oranges'");
+ checkAttributeAccess("Group", "\"City\"");
+ checkAttributeAccess("Order", "\"Address\"");
+ checkAttributeAccess("HavingClause", "\"ID\" <> 4");
+
+ final XIndexAccess orderColumns = m_composer.getOrderColumns();
+ assertTrue("Order columns doesn't exist: \"Address\"",
+ orderColumns != null && orderColumns.getCount() == 1 && orderColumns.getByIndex(0) != null);
+
+ final XIndexAccess groupColumns = m_composer.getGroupColumns();
+ assertTrue("Group columns doesn't exist: \"City\"",
+ groupColumns != null && groupColumns.getCount() == 1 && groupColumns.getByIndex(0) != null);
+
+ // XColumnsSupplier
+ final XColumnsSupplier xSelectColumns = UnoRuntime.queryInterface(XColumnsSupplier.class, m_composer);
+ assertTrue("no select columns, or wrong number of select columns",
+ xSelectColumns != null && xSelectColumns.getColumns() != null && xSelectColumns.getColumns().getElementNames().length == 6);
+
+ // structured filter
+ m_composer.setQuery("SELECT \"ID\", \"Postal\", \"Address\" FROM \"customers\"");
+ m_composer.setFilter(COMPLEXFILTER);
+ final PropertyValue[][] aStructuredFilter = m_composer.getStructuredFilter();
+ m_composer.setFilter("");
+ m_composer.setStructuredFilter(aStructuredFilter);
+ if (!m_composer.getFilter().equals(COMPLEXFILTER))
+ {
+ System.out.println(COMPLEXFILTER);
+ System.out.println(m_composer.getFilter());
+ }
+ assertTrue("Structured Filter not identical", m_composer.getFilter().equals(COMPLEXFILTER));
+
+ // structured having clause
+ m_composer.setHavingClause(COMPLEXFILTER);
+ final PropertyValue[][] aStructuredHaving = m_composer.getStructuredHavingClause();
+ m_composer.setHavingClause("");
+ m_composer.setStructuredHavingClause(aStructuredHaving);
+ assertTrue("Structured Having Clause not identical", m_composer.getHavingClause().equals(COMPLEXFILTER));
+ }
+
+ /** test various sub query related features ("queries in queries")
+ */
+ @Test
+ public void testSubQueries() throws Exception
+ {
+ m_composer.setQuery("SELECT * from \"" + INNERPRODUCTSQUERY + "\"");
+ final XTablesSupplier suppTables = UnoRuntime.queryInterface(XTablesSupplier.class, m_composer);
+ final XNameAccess tables = suppTables.getTables();
+ assertTrue("a simple SELECT * FROM <query> could not be parsed",
+ tables != null && tables.hasByName(INNERPRODUCTSQUERY));
+
+ final String sInnerCommand = m_database.getDatabase().getDataSource().getQueryDefinition(INNERPRODUCTSQUERY).getCommand();
+ final String sExecutableQuery = m_composer.getQueryWithSubstitution();
+ assertTrue("simple query containing a sub query improperly parsed to SDBC level statement: \n1. " + sExecutableQuery + "\n2. " + "SELECT * FROM ( " + sInnerCommand + " ) AS \"" + INNERPRODUCTSQUERY + "\"",
+ sExecutableQuery.equals("SELECT * FROM ( " + sInnerCommand + " ) AS \"" + INNERPRODUCTSQUERY + "\""));
+ }
+
+ /** tests the XParametersSupplier functionality
+ */
+ @Test
+ public void testParameters() throws Exception
+ {
+ // "orders for customers" is a query with a named parameter (based on another query)
+ m_database.getDatabase().getDataSource().createQuery("orders for customer", "SELECT * FROM \"all orders\" WHERE \"Customer Name\" LIKE :cname");
+ // "orders for customer and product" is query based on "orders for customers", adding an additional,
+ // anonymous parameter
+ m_database.getDatabase().getDataSource().createQuery("orders for customer and product", "SELECT * FROM \"orders for customer\" WHERE \"Product Name\" LIKE ?");
+
+ m_composer.setQuery(m_database.getDatabase().getDataSource().getQueryDefinition("orders for customer and product").getCommand());
+ final XParametersSupplier suppParams = UnoRuntime.queryInterface(XParametersSupplier.class, m_composer);
+ final XIndexAccess parameters = suppParams.getParameters();
+
+ final String expectedParamNames[] =
+
+ {
+ "cname",
+ "Product Name"
+ };
+
+ final int paramCount = parameters.getCount();
+ assertTrue("composer did find wrong number of parameters in the nested queries.",
+ paramCount == expectedParamNames.length);
+
+ for (int i = 0; i < paramCount; ++i)
+ {
+ final XPropertySet parameter = UnoRuntime.queryInterface(XPropertySet.class, parameters.getByIndex(i));
+ final String paramName = (String) parameter.getPropertyValue("Name");
+ assertTrue("wrong parameter name at position " + (i + 1) + " (expected: " + expectedParamNames[i] + ", found: " + paramName + ")",
+ paramName.equals(expectedParamNames[i]));
+
+ }
+ }
+
+ @Test
+ public void testConditionByColumn() throws Exception
+ {
+ m_composer.setQuery("SELECT * FROM \"customers\"");
+
+ final Object initArgs[] =
+
+ {
+ new NamedValue("AutomaticAddition", Boolean.TRUE)
+ };
+ final String serviceName = "com.sun.star.beans.PropertyBag";
+ final XPropertyContainer filter = UnoRuntime.queryInterface(XPropertyContainer.class, getMSF().createInstanceWithArguments(serviceName, initArgs));
+ filter.addProperty("Name", PropertyAttribute.MAYBEVOID, "Comment");
+ filter.addProperty("RealName", PropertyAttribute.MAYBEVOID, "Comment");
+ filter.addProperty("TableName", PropertyAttribute.MAYBEVOID, "customers");
+ filter.addProperty("Value", PropertyAttribute.MAYBEVOID, "Good one.");
+ filter.addProperty("Type", PropertyAttribute.MAYBEVOID, Integer.valueOf(DataType.LONGVARCHAR));
+ final XPropertySet column = UnoRuntime.queryInterface(XPropertySet.class, filter);
+
+ m_composer.appendFilterByColumn(column, true, SQLFilterOperator.LIKE);
+ assertTrue("At least one row should exist", m_database.getConnection().createStatement().executeQuery(m_composer.getQuery()).next());
+ }
+
+ private void impl_testDisjunctiveNormalForm(String _query, PropertyValue[][] _expectedDNF) throws SQLException
+ {
+ m_composer.setQuery(_query);
+
+ final PropertyValue[][] disjunctiveNormalForm = m_composer.getStructuredFilter();
+
+ assertEquals("DNF: wrong number of rows", _expectedDNF.length, disjunctiveNormalForm.length);
+ for (int i = 0; i < _expectedDNF.length; ++i)
+ {
+ assertEquals("DNF: wrong number of columns in row " + i, _expectedDNF[i].length, disjunctiveNormalForm[i].length);
+ for (int j = 0; j < _expectedDNF[i].length; ++j)
+ {
+ assertEquals("DNF: wrong content in column " + j + ", row " + i,
+ _expectedDNF[i][j].Name, disjunctiveNormalForm[i][j].Name);
+ }
+ }
+ }
+
+ /** tests the disjunctive normal form functionality, aka the structured filter,
+ * of the composer
+ */
+ @Test
+ public void testDisjunctiveNormalForm() throws Exception
+ {
+ // a simple case: WHERE clause simply is a combination of predicates knitted with AND
+ String query =
+ "SELECT \"customers\".\"Name\", "
+ + "\"customers\".\"Address\", "
+ + "\"customers\".\"City\", "
+ + "\"customers\".\"Postal\", "
+ + "\"products\".\"Name\" "
+ + "FROM \"orders\", \"customers\", \"orders_details\", \"products\" "
+ + "WHERE ( \"orders\".\"CustomerID\" = \"customers\".\"ID\" "
+ + "AND \"orders_details\".\"OrderID\" = \"orders\".\"ID\" "
+ + "AND \"orders_details\".\"ProductID\" = \"products\".\"ID\" "
+ + ") ";
+
+ impl_testDisjunctiveNormalForm(query, new PropertyValue[][]
+ {
+ new PropertyValue[]
+ {
+ new PropertyValue("CustomerID", SQLFilterOperator.EQUAL, "\"customers\".\"ID\"", PropertyState.DIRECT_VALUE),
+ new PropertyValue("OrderID", SQLFilterOperator.EQUAL, "\"orders\".\"ID\"", PropertyState.DIRECT_VALUE),
+ new PropertyValue("ProductID", SQLFilterOperator.EQUAL, "\"products\".\"ID\"", PropertyState.DIRECT_VALUE)
+ }
+ });
+
+ // somewhat more challenging: One of the conjunction terms is a disjunction itself
+ query =
+ "SELECT \"customers\".\"Name\", "
+ + "\"customers\".\"Address\", "
+ + "\"customers\".\"City\", "
+ + "\"customers\".\"Postal\", "
+ + "\"products\".\"Name\" "
+ + "FROM \"orders\", \"customers\", \"orders_details\", \"products\" "
+ + "WHERE ( \"orders\".\"CustomerID\" = \"customers\".\"ID\" "
+ + "AND \"orders_details\".\"OrderID\" = \"orders\".\"ID\" "
+ + "AND \"orders_details\".\"ProductID\" = \"products\".\"ID\" "
+ + ") "
+ + "AND "
+ + "( \"products\".\"Name\" = 'Apples' "
+ + "OR \"products\".\"ID\" = 2 "
+ + ")";
+
+ impl_testDisjunctiveNormalForm(query, new PropertyValue[][]
+ {
+ new PropertyValue[]
+ {
+ new PropertyValue("CustomerID", SQLFilterOperator.EQUAL, "\"customers\".\"ID\"", PropertyState.DIRECT_VALUE),
+ new PropertyValue("OrderID", SQLFilterOperator.EQUAL, "\"orders\".\"ID\"", PropertyState.DIRECT_VALUE),
+ new PropertyValue("ProductID", SQLFilterOperator.EQUAL, "\"products\".\"ID\"", PropertyState.DIRECT_VALUE),
+ new PropertyValue("Name", SQLFilterOperator.EQUAL, "Apples", PropertyState.DIRECT_VALUE)
+ },
+ new PropertyValue[]
+ {
+ new PropertyValue("CustomerID", SQLFilterOperator.EQUAL, "\"customers\".\"ID\"", PropertyState.DIRECT_VALUE),
+ new PropertyValue("OrderID", SQLFilterOperator.EQUAL, "\"orders\".\"ID\"", PropertyState.DIRECT_VALUE),
+ new PropertyValue("ProductID", SQLFilterOperator.EQUAL, "\"products\".\"ID\"", PropertyState.DIRECT_VALUE),
+ new PropertyValue("ID", SQLFilterOperator.EQUAL, Integer.valueOf(2), PropertyState.DIRECT_VALUE)
+ }
+ });
+
+ }
+}
diff --git a/dbaccess/qa/complex/dbaccess/TestCase.java b/dbaccess/qa/complex/dbaccess/TestCase.java
new file mode 100644
index 0000000000..8c284ae51f
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/TestCase.java
@@ -0,0 +1,232 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package complex.dbaccess;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import com.sun.star.beans.PropertyValue;
+import com.sun.star.frame.XComponentLoader;
+import com.sun.star.frame.XModel;
+import com.sun.star.lang.XMultiServiceFactory;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.uno.XComponentContext;
+import helper.FileTools;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+// ---------- junit imports -----------------
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.openoffice.test.OfficeConnection;
+import static org.junit.Assert.*;
+
+
+
+public abstract class TestCase
+{
+
+ protected final XComponentContext getComponentContext()
+ {
+ return connection.getComponentContext();
+ }
+
+
+ public void before() throws java.lang.Exception
+ {
+ }
+
+
+ public void after() throws java.lang.Exception
+ {
+ }
+
+
+ /** returns the URL of a temporary file which can be used during the test.
+ *
+ * The file will be deleted when the process exits
+ * @return the URL of a temporary file
+ */
+ protected final String createTempFileURL() throws IOException
+ {
+ final File documentFile = java.io.File.createTempFile( "dbaccess_test", ".odb" ).getAbsoluteFile();
+ if ( documentFile.exists() )
+ {
+ documentFile.delete();
+ }
+ return FileHelper.getOOoCompatibleFileURL( documentFile.toURI().toURL().toString() );
+ }
+
+
+ /**
+ * copies the file given by URL to a temporary file
+ * @return
+ * the URL of the new file
+ */
+ protected final String copyToTempFile( String _sourceURL ) throws IOException
+ {
+ final String targetURL = createTempFileURL();
+ try
+ {
+ FileTools.copyFile( new File( new URI( _sourceURL ) ), new File( new URI( targetURL ) ) );
+ }
+ catch ( URISyntaxException e ) { }
+
+ return FileHelper.getOOoCompatibleFileURL( targetURL );
+ }
+
+
+ protected final XModel loadDocument( final String _docURL ) throws Exception
+ {
+ final XComponentLoader loader = UnoRuntime.queryInterface( XComponentLoader.class,
+ getMSF().createInstance( "com.sun.star.frame.Desktop" ) );
+ return UnoRuntime.queryInterface( XModel.class,
+ loader.loadComponentFromURL( _docURL, "_blank", 0, new PropertyValue[] {} ) );
+ }
+
+
+ /** invokes a given method on a given object, and assures a certain exception is caught
+ * @param _message
+ * is the message to print when the check fails
+ * @param _object
+ * is the object to invoke the method on
+ * @param _methodName
+ * is the name of the method to invoke
+ * @param _methodArgs
+ * are the arguments to pass to the method.
+ * @param _argClasses
+ * are the classes to assume for the arguments of the methods
+ * @param _expectedExceptionClass
+ * is the class of the exception to be caught. If this is null,
+ * it means that <em>no</em> exception must be throw by invoking the method.
+ */
+ private void assureException( final String _message, final Object _object, final String _methodName,
+ final Class[] _argClasses, final Object[] _methodArgs, final Class _expectedExceptionClass )
+ {
+ Class<?> objectClass = _object.getClass();
+
+ boolean noExceptionAllowed = ( _expectedExceptionClass == null );
+
+ boolean caughtExpected = noExceptionAllowed;
+ try
+ {
+ Method method = objectClass.getMethod( _methodName, _argClasses );
+ method.invoke(_object, _methodArgs );
+ }
+ catch ( InvocationTargetException e )
+ {
+ caughtExpected = noExceptionAllowed
+ ? false
+ : ( e.getTargetException().getClass().equals( _expectedExceptionClass ) );
+ }
+ catch( Exception e )
+ {
+ caughtExpected = false;
+ }
+
+ assertTrue( _message, caughtExpected );
+ }
+
+ /** invokes a given method on a given object, and assures a certain exception is caught
+ * @param _message is the message to print when the check fails
+ * @param _object is the object to invoke the method on
+ * @param _methodName is the name of the method to invoke
+ * @param _methodArgs are the arguments to pass to the method. Those implicitly define
+ * the classes of the arguments of the method which is called.
+ * @param _expectedExceptionClass is the class of the exception to be caught. If this is null,
+ * it means that <em>no</em> exception must be throw by invoking the method.
+ */
+ private void assureException( final String _message, final Object _object, final String _methodName,
+ final Object[] _methodArgs, final Class _expectedExceptionClass )
+ {
+ Class[] argClasses = new Class[ _methodArgs.length ];
+ for ( int i=0; i<_methodArgs.length; ++i )
+ argClasses[i] = _methodArgs[i].getClass();
+ assureException( _message, _object, _methodName, argClasses, _methodArgs, _expectedExceptionClass );
+ }
+
+ /** invokes a given method on a given object, and assures a certain exception is caught
+ * @param _object is the object to invoke the method on
+ * @param _methodName is the name of the method to invoke
+ * @param _methodArgs are the arguments to pass to the method. Those implicitly define
+ * the classes of the arguments of the method which is called.
+ * @param _expectedExceptionClass is the class of the exception to be caught. If this is null,
+ * it means that <em>no</em> exception must be throw by invoking the method.
+ */
+ private void assureException( final Object _object, final String _methodName, final Object[] _methodArgs,
+ final Class _expectedExceptionClass )
+ {
+ assureException(
+ "did not catch the expected exception (" +
+ ( ( _expectedExceptionClass == null ) ? "none" : _expectedExceptionClass.getName() ) +
+ ") while calling " + _object.getClass().getName() + "." + _methodName,
+ _object, _methodName, _methodArgs, _expectedExceptionClass );
+ }
+
+ /** invokes a given method on a given object, and assures a certain exception is caught
+ * @param _object is the object to invoke the method on
+ * @param _methodName is the name of the method to invoke
+ * @param _methodArgs are the arguments to pass to the method
+ * @param _argClasses are the classes to assume for the arguments of the methods
+ * @param _expectedExceptionClass is the class of the exception to be caught. If this is null,
+ * it means that <em>no</em> exception must be throw by invoking the method.
+ */
+ protected void assureException( final Object _object, final String _methodName, final Class[] _argClasses,
+ final Object[] _methodArgs, final Class _expectedExceptionClass )
+ {
+ assureException(
+ "did not catch the expected exception (" +
+ ( ( _expectedExceptionClass == null ) ? "none" : _expectedExceptionClass.getName() ) +
+ ") while calling " + _object.getClass().getName() + "." + _methodName,
+ _object, _methodName, _argClasses, _methodArgs, _expectedExceptionClass );
+ }
+
+
+ @SuppressWarnings("unchecked")
+ protected void assureException( Object _object, Class _unoInterfaceClass, String _methodName, Object[] _methodArgs,
+ Class _expectedExceptionClass )
+ {
+ assureException( UnoRuntime.queryInterface( _unoInterfaceClass, _object ), _methodName,
+ _methodArgs, _expectedExceptionClass );
+ }
+
+
+ protected final XMultiServiceFactory getMSF()
+ {
+ return UnoRuntime.queryInterface(XMultiServiceFactory.class, connection.getComponentContext().getServiceManager());
+ }
+
+
+ // setup and close connections
+ @BeforeClass
+ public static void setUpConnection() throws Exception
+ {
+ connection.setUp();
+ }
+
+
+ @AfterClass
+ public static void tearDownConnection() throws InterruptedException, com.sun.star.uno.Exception
+ {
+ connection.tearDown();
+ }
+
+ private static final OfficeConnection connection = new OfficeConnection();
+
+}
diff --git a/dbaccess/qa/complex/dbaccess/UISettings.java b/dbaccess/qa/complex/dbaccess/UISettings.java
new file mode 100644
index 0000000000..273d6b7a70
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/UISettings.java
@@ -0,0 +1,132 @@
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+package complex.dbaccess;
+
+import com.sun.star.awt.FontSlant;
+import com.sun.star.awt.TextAlign;
+import com.sun.star.beans.XPropertySet;
+import com.sun.star.container.XNameAccess;
+import com.sun.star.form.runtime.XFormController;
+import com.sun.star.frame.XController;
+import com.sun.star.sdb.application.DatabaseObject;
+import com.sun.star.uno.UnoRuntime;
+import com.sun.star.util.XCloseable;
+import connectivity.tools.CRMDatabase;
+
+// ---------- junit imports -----------------
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+
+public class UISettings extends TestCase
+{
+
+ /** verifies that aliases for inner queries work as expected
+ */
+ @Test
+ public void checkTableFormattingPersistence() throws java.lang.Exception
+ {
+ // create, load, and connect a DB doc
+ CRMDatabase database = new CRMDatabase( getMSF(), true );
+
+ // display a table
+ XFormController tableViewController = UnoRuntime.queryInterface( XFormController.class,
+ database.loadSubComponent( DatabaseObject.TABLE, "customers" ) );
+ XPropertySet tableControlModel = UnoRuntime.queryInterface( XPropertySet.class,
+ tableViewController.getCurrentControl().getModel() );
+
+ // change the table's formatting
+ tableControlModel.setPropertyValue( "FontName", "Andale Sans UI" );
+ tableControlModel.setPropertyValue( "FontHeight", Float.valueOf( 20 ) );
+ tableControlModel.setPropertyValue( "FontSlant", FontSlant.ITALIC );
+
+ String docURL = database.getDatabase().getModel().getURL();
+
+ // save close the database document
+ database.saveAndClose();
+
+ // load a copy of the document
+ // normally, it should be sufficient to load the same doc. However, there might be objects in the Java VM
+ // which are not yet freed, and which effectively hold the document alive. More precise: The document (|doc|)
+ // is certainly disposed, but other objects might hold a reference to one of the many other components
+ // around the database document, the data source, the connection, etc. As long as those objects are
+ // not cleaned up, the "database model impl" - the structure holding all document data - will
+ // stay alive, and subsequent requests to load the doc will just reuse it, without really loading it.
+ docURL = copyToTempFile( docURL );
+ loadDocument( docURL );
+ database = new CRMDatabase( getMSF(), docURL );
+
+ // display the table, again
+ tableViewController = UnoRuntime.queryInterface( XFormController.class,
+ database.loadSubComponent( DatabaseObject.TABLE, "customers" ) );
+ tableControlModel = UnoRuntime.queryInterface( XPropertySet.class,
+ tableViewController.getCurrentControl().getModel() );
+
+ // verify the properties
+ assertEquals( "wrong font name", "Andale Sans UI", tableControlModel.getPropertyValue( "FontName" ) );
+ assertEquals( "wrong font height", 20, ((Float)tableControlModel.getPropertyValue( "FontHeight" )).floatValue(), 0 );
+ assertEquals( "wrong font slant", FontSlant.ITALIC, tableControlModel.getPropertyValue( "FontSlant" ) );
+
+ // close the doc
+ database.saveAndClose();
+ }
+
+ /**
+ * checks whether query columns use the settings of the underlying table column, if they do not (yet) have own
+ * settings
+ */
+ @Test
+ public void checkTransparentQueryColumnSettings() throws java.lang.Exception
+ {
+ // create, load, and connect a DB doc
+ CRMDatabase database = new CRMDatabase( getMSF(), true );
+
+ // display a table
+ XController tableView = database.loadSubComponent( DatabaseObject.TABLE, "customers" );
+ XFormController tableViewController = UnoRuntime.queryInterface( XFormController.class,
+ tableView );
+ XNameAccess tableControlModel = UnoRuntime.queryInterface( XNameAccess.class,
+ tableViewController.getCurrentControl().getModel() );
+
+ // change the formatting of a table column
+ XPropertySet idColumn = UnoRuntime.queryInterface( XPropertySet.class, tableControlModel.getByName( "ID" ) );
+ assertTrue( "precondition not met: column already centered",
+ ((Short)idColumn.getPropertyValue( "Align" )).shortValue() != TextAlign.CENTER );
+ idColumn.setPropertyValue( "Align", TextAlign.CENTER );
+
+ // close the table data view
+ XCloseable closeSubComponent = UnoRuntime.queryInterface( XCloseable.class, tableView.getFrame() );
+ closeSubComponent.close( true );
+
+ // create a query based on that column
+ database.getDatabase().getDataSource().createQuery( "q_customers", "SELECT * FROM \"customers\"" );
+
+ // load this query, and verify the table column settings was propagated to the query column
+ XFormController queryViewController = UnoRuntime.queryInterface( XFormController.class,
+ database.loadSubComponent( DatabaseObject.QUERY, "q_customers" ) );
+ tableControlModel = UnoRuntime.queryInterface( XNameAccess.class,
+ queryViewController.getCurrentControl().getModel() );
+ idColumn = UnoRuntime.queryInterface( XPropertySet.class, tableControlModel.getByName( "ID" ) );
+
+ assertTrue( "table column alignment was not propagated to the query column",
+ ((Short)idColumn.getPropertyValue( "Align" )).shortValue() == TextAlign.CENTER );
+
+ // save close the database document
+ database.saveAndClose();
+ }
+}
diff --git a/dbaccess/qa/complex/dbaccess/makefile.mk b/dbaccess/qa/complex/dbaccess/makefile.mk
new file mode 100644
index 0000000000..862a0ffc96
--- /dev/null
+++ b/dbaccess/qa/complex/dbaccess/makefile.mk
@@ -0,0 +1,93 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+.IF "$(OOO_JUNIT_JAR)" == ""
+nothing .PHONY:
+ @echo -----------------------------------------------------
+ @echo - JUnit not available, not building anything
+ @echo -----------------------------------------------------
+.ELSE
+
+PRJ = ../../..
+PRJNAME = dbaccess
+TARGET = qa_complex_dbaccess
+PACKAGE = complex/dbaccess
+
+# --- Settings -----------------------------------------------------
+.INCLUDE: settings.mk
+
+#----- compile .java files -----------------------------------------
+
+JARFILES = OOoRunner.jar ridl.jar test.jar juh.jar unoil.jar ConnectivityTools.jar
+EXTRAJARFILES = $(OOO_JUNIT_JAR)
+
+#----- create a jar from compiled files ----------------------------
+
+JARTARGET = $(TARGET).jar
+
+#----- Java files --------------------------------------------------
+
+# here store only Files which contain a @Test
+JAVATESTFILES = \
+ ApplicationController.java \
+ Beamer.java \
+ DataSource.java \
+ DatabaseDocument.java \
+ Parser.java \
+ PropertyBag.java \
+ Query.java \
+ QueryInQuery.java \
+ RowSet.java \
+ SingleSelectQueryComposer.java \
+ UISettings.java \
+ CopyTableWizard.java \
+
+# put here all other files
+JAVAFILES = $(JAVATESTFILES) \
+ CRMBasedTestCase.java \
+ CopyTableInterActionHandler.java \
+ DatabaseApplication.java \
+ FileHelper.java \
+ RowSetEventListener.java \
+ TestCase.java \
+
+
+# Sample how to debug
+# JAVAIFLAGS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=9003,suspend=y
+
+# --- Targets ------------------------------------------------------
+
+.INCLUDE: target.mk
+
+ALL : ALLTAR
+
+# --- subsequent tests ---------------------------------------------
+
+.IF "$(OOO_SUBSEQUENT_TESTS)" != ""
+
+.INCLUDE: installationtest.mk
+
+ALLTAR : javatest
+
+ # Sample how to debug
+ # JAVAIFLAGS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=9003,suspend=y
+
+.END # "$(OOO_SUBSEQUENT_TESTS)" == ""
+
+.END # ELSE "$(OOO_JUNIT_JAR)" != ""
+