diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /dbaccess/qa | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
51 files changed, 6405 insertions, 0 deletions
diff --git a/dbaccess/qa/complex/dbaccess/ApplicationController.java b/dbaccess/qa/complex/dbaccess/ApplicationController.java new file mode 100644 index 000000000..eafc32dba --- /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 000000000..3342c70df --- /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 000000000..a61ffdfa7 --- /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 000000000..57f4172e3 --- /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 000000000..a9254fac3 --- /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 int 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 000000000..0413a6e07 --- /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 000000000..dad7644f8 --- /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 000000000..5bd28053b --- /dev/null +++ b/dbaccess/qa/complex/dbaccess/DatabaseDocument.java @@ -0,0 +1,995 @@ +/* + * 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() + }; + } + + 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 000000000..cae67af00 --- /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 000000000..8025e1d37 --- /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 000000000..6807569ec --- /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 000000000..8881bb3ed --- /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 000000000..4e6a9d0ff --- /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 000000000..4c5fcc6da --- /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 000000000..991dfb7af --- /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 000000000..2d05030b7 --- /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 000000000..a9768e7b4 --- /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 000000000..273d6b7a7 --- /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 000000000..862a0ffc9 --- /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)" != "" + diff --git a/dbaccess/qa/extras/dialog-save.cxx b/dbaccess/qa/extras/dialog-save.cxx new file mode 100644 index 000000000..212127d48 --- /dev/null +++ b/dbaccess/qa/extras/dialog-save.cxx @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + */ + +#include <sal/config.h> +#include <test/unoapi_test.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/document/XEmbeddedScripts.hpp> +#include <com/sun/star/script/XStorageBasedLibraryContainer.hpp> +#include <com/sun/star/script/XLibraryContainer.hpp> +#include <com/sun/star/util/XModifiable.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> + +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + + +class DialogSaveTest : public UnoApiTest +{ +public: + DialogSaveTest(); + + void test(); + + CPPUNIT_TEST_SUITE(DialogSaveTest); +// Should we disable this test on MOX and WNT? +// #if !defined(MACOSX) && !defined(_WIN32) + CPPUNIT_TEST(test); +// #endif + CPPUNIT_TEST_SUITE_END(); + +}; + + +DialogSaveTest::DialogSaveTest() + : UnoApiTest("/dbaccess/qa/extras/testdocuments") +{ +} + +void DialogSaveTest::test() +{ + const OUString aFileName(m_directories.getURLFromWorkdir(u"CppunitTest/testDialogSave.odb")); + { + uno::Reference< lang::XComponent > xComponent = loadFromDesktop(aFileName); + uno::Reference< frame::XStorable > xDocStorable(xComponent, UNO_QUERY_THROW); + uno::Reference< document::XEmbeddedScripts > xDocScr(xComponent, UNO_QUERY_THROW); + uno::Reference< script::XStorageBasedLibraryContainer > xStorBasLib(xDocScr->getBasicLibraries()); + CPPUNIT_ASSERT(xStorBasLib.is()); + uno::Reference< script::XLibraryContainer > xBasLib(xStorBasLib, UNO_QUERY_THROW); + uno::Reference< script::XStorageBasedLibraryContainer > xStorDlgLib(xDocScr->getDialogLibraries()); + CPPUNIT_ASSERT(xStorDlgLib.is()); + uno::Reference< script::XLibraryContainer > xDlgLib(xStorDlgLib, UNO_QUERY_THROW); + static const OUStringLiteral sStandard(u"Standard"); + xBasLib->loadLibrary(sStandard); + CPPUNIT_ASSERT(xBasLib->isLibraryLoaded(sStandard)); + // the whole point of this test is to test the "save" operation + // when the Basic library is loaded, but not the Dialog library + CPPUNIT_ASSERT(!xDlgLib->isLibraryLoaded(sStandard)); + + // make some change to enable a save + // uno::Reference< document::XDocumentPropertiesSupplier > xDocPropSuppl(xComponent, UNO_QUERY_THROW); + // uno::Reference< document::XDocumentPropertiesSupplier > xDocProps(xDocPropSuppl->getDocumentProperties()); + // CPPUNIT_ASSERT(xDocProps.is()); + // xDocProps.setTitle(xDocProps.getTitle() + " suffix"); + uno::Reference< util::XModifiable > xDocMod(xComponent, UNO_QUERY_THROW); + xDocMod->setModified(true); + + // now save; the code path to exercise in this test is the "store to same location" + // do *not* change to store(As|To|URL)! + xDocStorable->store(); + + // close + uno::Reference< util::XCloseable > xDocCloseable(xComponent, UNO_QUERY_THROW); + xDocCloseable->close(false); + + // All our uno::References are (should?) be invalid now -> let them go out of scope + } + { + uno::Sequence<uno::Any> args{ uno::Any(aFileName) }; + Reference<container::XHierarchicalNameAccess> xHNA(getMultiServiceFactory()->createInstanceWithArguments("com.sun.star.packages.Package", args), UNO_QUERY_THROW); + Reference< beans::XPropertySet > xPS(xHNA->getByHierarchicalName("Dialogs/Standard/Dialog1.xml"), UNO_QUERY_THROW); + sal_Int64 nSize = 0; + CPPUNIT_ASSERT(xPS->getPropertyValue("Size") >>= nSize); + CPPUNIT_ASSERT(nSize != 0); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(DialogSaveTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/dbaccess/qa/extras/empty-stdlib-save.cxx b/dbaccess/qa/extras/empty-stdlib-save.cxx new file mode 100644 index 000000000..440fc19d4 --- /dev/null +++ b/dbaccess/qa/extras/empty-stdlib-save.cxx @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + */ + +#include <sal/config.h> +#include <test/unoapi_test.hxx> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/document/XEmbeddedScripts.hpp> +#include <com/sun/star/script/XStorageBasedLibraryContainer.hpp> +#include <com/sun/star/script/XLibraryContainer.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> + +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + + +class DialogSaveTest : public UnoApiTest +{ +public: + DialogSaveTest(); + + void test(); + + CPPUNIT_TEST_SUITE(DialogSaveTest); +// Should we disable this test on MOX and WNT? +// #if !defined(MACOSX) && !defined(_WIN32) + CPPUNIT_TEST(test); +// #endif + CPPUNIT_TEST_SUITE_END(); + +}; + + +DialogSaveTest::DialogSaveTest() + : UnoApiTest("/dbaccess/qa/extras/testdocuments") +{ +} + +void DialogSaveTest::test() +{ + const OUString aFileName(m_directories.getURLFromWorkdir(u"CppunitTest/testEmptyStdlibSave.odb")); + { + uno::Reference< lang::XComponent > xComponent = loadFromDesktop(aFileName); + uno::Reference< frame::XStorable > xDocStorable(xComponent, UNO_QUERY_THROW); + uno::Reference< document::XEmbeddedScripts > xDocScr(xComponent, UNO_QUERY_THROW); + uno::Reference< script::XStorageBasedLibraryContainer > xStorBasLib(xDocScr->getBasicLibraries()); + CPPUNIT_ASSERT(xStorBasLib.is()); + uno::Reference< script::XLibraryContainer > xBasLib(xStorBasLib, UNO_QUERY_THROW); + uno::Reference< script::XStorageBasedLibraryContainer > xStorDlgLib(xDocScr->getDialogLibraries()); + CPPUNIT_ASSERT(xStorDlgLib.is()); + uno::Reference< script::XLibraryContainer > xDlgLib(xStorDlgLib, UNO_QUERY_THROW); + static const OUStringLiteral sStandard(u"Standard"); + xBasLib->loadLibrary(sStandard); + xDlgLib->loadLibrary(sStandard); + CPPUNIT_ASSERT(xBasLib->isLibraryLoaded(sStandard)); + CPPUNIT_ASSERT(xDlgLib->isLibraryLoaded(sStandard)); + + Any a; + uno::Reference< container::XNameContainer > xI; + + a = xBasLib->getByName(sStandard); + a >>= xI; + CPPUNIT_ASSERT(xI.is()); + xI->removeByName("Raralix"); + + a = xDlgLib->getByName(sStandard); + a >>= xI; + CPPUNIT_ASSERT(xI.is()); + xI->removeByName("Dialog1"); + + // uno::Reference< util::XModifiable > xDlgMod(xDlgLib, UNO_QUERY_THROW); + // xDlgMod->setModified(sal_True); + + // uno::Reference< util::XModifiable > xScrMod(xDocScr, UNO_QUERY_THROW); + // xScrMod->setModified(sal_True); + + // uno::Reference< util::XModifiable > xDocMod(xComponent, UNO_QUERY_THROW); + // std::cerr << "** Modified: " << static_cast<bool>(xDocMod->isModified()) << std::endl; + // xDocMod->setModified(sal_True); + // std::cerr << "** Modified: " << static_cast<bool>(xDocMod->isModified()) << std::endl; + // CPPUNIT_ASSERT(xDocMod->isModified()); + + // now save; the code path to exercise in this test is the "store to same location" + // do *not* change to store(As|To|URL)! + xDocStorable->store(); + + // close + uno::Reference< util::XCloseable > xDocCloseable(xComponent, UNO_QUERY_THROW); + xDocCloseable->close(false); + + // All our uno::References are (should?) be invalid now -> let them go out of scope + } + { + uno::Sequence<uno::Any> args{ uno::Any(aFileName) }; + Reference<container::XHierarchicalNameAccess> xHNA(getMultiServiceFactory()->createInstanceWithArguments("com.sun.star.packages.Package", args), UNO_QUERY_THROW); + CPPUNIT_ASSERT(!xHNA->hasByHierarchicalName("Basic/Standard")); + CPPUNIT_ASSERT(!xHNA->hasByHierarchicalName("Dialogs/Standard")); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(DialogSaveTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/dbaccess/qa/extras/hsql_schema_import.cxx b/dbaccess/qa/extras/hsql_schema_import.cxx new file mode 100644 index 000000000..f5f34d38b --- /dev/null +++ b/dbaccess/qa/extras/hsql_schema_import.cxx @@ -0,0 +1,210 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + */ + +#include <fbcreateparser.hxx> +#include <columndef.hxx> +#include <cppunit/plugin/TestPlugIn.h> +#include <com/sun/star/sdbc/DataType.hpp> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +using namespace dbahsql; + +namespace +{ +constexpr std::size_t operator"" _z(unsigned long long n) { return n; } + +const ColumnDefinition* lcl_findByType(const std::vector<ColumnDefinition>& columns, + sal_Int32 nType) +{ + for (const auto& column : columns) + { + if (column.getDataType() == nType) + return &column; + } + return nullptr; +} +} + +class HsqlSchemaImportTest : public CppUnit::TestFixture +{ +public: + void testIntegerPrimaryKeyNotNull(); + void testVarcharWithParam(); + void testVarcharWithoutParam(); + void testNumericWithTwoParam(); + void testIntegerAutoincremental(); + void testTimestampWithParam(); + void testDefaultValueNow(); + void testEvilNullColumnName(); + // TODO testForeign, testDecomposer + + CPPUNIT_TEST_SUITE(HsqlSchemaImportTest); + + CPPUNIT_TEST(testIntegerPrimaryKeyNotNull); + CPPUNIT_TEST(testVarcharWithParam); + CPPUNIT_TEST(testVarcharWithoutParam); + CPPUNIT_TEST(testNumericWithTwoParam); + CPPUNIT_TEST(testIntegerAutoincremental); + CPPUNIT_TEST(testTimestampWithParam); + CPPUNIT_TEST(testDefaultValueNow); + CPPUNIT_TEST(testEvilNullColumnName); + + CPPUNIT_TEST_SUITE_END(); +}; + +void HsqlSchemaImportTest::testIntegerPrimaryKeyNotNull() +{ + FbCreateStmtParser aCreateParser; + aCreateParser.parse(u"CREATE CACHED TABLE \"myTable\"(\"id\" INTEGER NOT NULL PRIMARY KEY)"); + + CPPUNIT_ASSERT_EQUAL(OUString{ "\"myTable\"" }, aCreateParser.getTableName()); + const auto& columns = aCreateParser.getColumnDef(); + CPPUNIT_ASSERT_EQUAL(1_z, columns.size()); + const auto& column = columns.at(0); + CPPUNIT_ASSERT_EQUAL(OUString{ "\"id\"" }, column.getName()); + CPPUNIT_ASSERT_EQUAL(css::sdbc::DataType::INTEGER, column.getDataType()); + CPPUNIT_ASSERT(column.isPrimaryKey()); + CPPUNIT_ASSERT(!column.isNullable()); + CPPUNIT_ASSERT(!column.isAutoIncremental()); +} + +void HsqlSchemaImportTest::testVarcharWithParam() +{ + FbCreateStmtParser aCreateParser; + aCreateParser.parse( + u"CREATE CACHED TABLE \"myTable\"(\"id\" INTEGER NOT NULL PRIMARY KEY, \"myText\" " + "VARCHAR(50))"); + + const auto& columns = aCreateParser.getColumnDef(); + CPPUNIT_ASSERT_EQUAL(2_z, columns.size()); + const ColumnDefinition* colVarchar = lcl_findByType(columns, css::sdbc::DataType::VARCHAR); + CPPUNIT_ASSERT(colVarchar != nullptr); + const auto& params = colVarchar->getParams(); + CPPUNIT_ASSERT_EQUAL(1_z, params.size()); + constexpr sal_Int32 nParamExpected = 50; + CPPUNIT_ASSERT_EQUAL(nParamExpected, params.at(0)); // VARCHAR(50) +} + +/** + * Special case: + * HSQLDB might define a column VARCHAR without parameter. With Firebird + * dialect, this is forbidden, so a default parameter has to be appended: + **/ +void HsqlSchemaImportTest::testVarcharWithoutParam() +{ + FbCreateStmtParser aCreateParser; + aCreateParser.parse( + u"CREATE CACHED TABLE \"myTable\"(\"id\" INTEGER NOT NULL PRIMARY KEY, \"myText\" " + "VARCHAR)"); + + const auto& columns = aCreateParser.getColumnDef(); + CPPUNIT_ASSERT_EQUAL(2_z, columns.size()); + const ColumnDefinition* colVarchar = lcl_findByType(columns, css::sdbc::DataType::VARCHAR); + CPPUNIT_ASSERT(colVarchar != nullptr); + const auto& params = colVarchar->getParams(); + CPPUNIT_ASSERT_EQUAL(1_z, params.size()); // parameter generated +} + +void HsqlSchemaImportTest::testNumericWithTwoParam() +{ + FbCreateStmtParser aCreateParser; + aCreateParser.parse( + u"CREATE CACHED TABLE \"myTable\"(\"id\" INTEGER NOT NULL PRIMARY KEY, \"Betrag\" " + "NUMERIC(8,2))"); + + const auto& columns = aCreateParser.getColumnDef(); + CPPUNIT_ASSERT_EQUAL(2_z, columns.size()); + + const ColumnDefinition* colNumeric = lcl_findByType(columns, css::sdbc::DataType::NUMERIC); + CPPUNIT_ASSERT(colNumeric != nullptr); + const auto& params = colNumeric->getParams(); + CPPUNIT_ASSERT_EQUAL(2_z, params.size()); + + constexpr sal_Int32 nPrecision = 8; + constexpr sal_Int32 nScale = 2; + CPPUNIT_ASSERT_EQUAL(nPrecision, params.at(0)); + CPPUNIT_ASSERT_EQUAL(nScale, params.at(1)); +} + +void HsqlSchemaImportTest::testIntegerAutoincremental() +{ + FbCreateStmtParser aCreateParser; + aCreateParser.parse( + u"CREATE CACHED TABLE \"myTable\"(\"id\" INTEGER NOT NULL PRIMARY KEY GENERATED " + "BY DEFAULT AS IDENTITY(START WITH 0), \"myText\" VARCHAR(50))"); + + const auto& columns = aCreateParser.getColumnDef(); + const auto column = columns.at(0); + CPPUNIT_ASSERT_EQUAL(css::sdbc::DataType::INTEGER, column.getDataType()); + CPPUNIT_ASSERT(column.isAutoIncremental()); + CPPUNIT_ASSERT(column.isPrimaryKey()); + CPPUNIT_ASSERT(!column.isNullable()); +} + +/** + * Special case: + * Hsqldb might use one parameter for defining column with type TIMESTAMP. + * With Firebird this is illegal. + */ +void HsqlSchemaImportTest::testTimestampWithParam() +{ + FbCreateStmtParser aCreateParser; + aCreateParser.parse( + u"CREATE CACHED TABLE \"myTable\"(\"id\" INTEGER NOT NULL PRIMARY KEY, \"myText\" " + "TIMESTAMP(0))"); + + const auto& columns = aCreateParser.getColumnDef(); + const ColumnDefinition* colTimeStamp = lcl_findByType(columns, css::sdbc::DataType::TIMESTAMP); + + CPPUNIT_ASSERT(colTimeStamp != nullptr); + + // instead of asserting parameter size, look at the deparsed string, + // because it's Firebird specific + OUString fbSql = aCreateParser.compose(); + CPPUNIT_ASSERT(fbSql.indexOf("0") < 0); //does not contain +} + +/** + * Special case: + * HSQLDB uses keyword NOW without quotes. Firebird uses single quotes 'NOW' + */ +void HsqlSchemaImportTest::testDefaultValueNow() +{ + FbCreateStmtParser aCreateParser; + aCreateParser.parse( + u"CREATE CACHED TABLE \"myTable\"(\"id\" INTEGER NOT NULL PRIMARY KEY, \"myDate\" " + "TIMESTAMP DEFAULT NOW)"); + + const auto& columns = aCreateParser.getColumnDef(); + const ColumnDefinition* colTimeStamp = lcl_findByType(columns, css::sdbc::DataType::TIMESTAMP); + + CPPUNIT_ASSERT(colTimeStamp != nullptr); + CPPUNIT_ASSERT_EQUAL(OUString{ "NOW" }, colTimeStamp->getDefault()); // parsed NOW + OUString fbSql = aCreateParser.compose(); + CPPUNIT_ASSERT(fbSql.indexOf("\'NOW\'") > 0); // composed 'NOW' +} + +void HsqlSchemaImportTest::testEvilNullColumnName() +{ + FbCreateStmtParser aCreateParser; + aCreateParser.parse(u"CREATE CACHED TABLE \"myTable\"(\"id\" INTEGER NOT NULL PRIMARY KEY, " + "\"myEvilNOT NULLName\" " + "VARCHAR(20))"); + + const auto& columns = aCreateParser.getColumnDef(); + CPPUNIT_ASSERT_EQUAL(2_z, columns.size()); + const ColumnDefinition* colVarchar = lcl_findByType(columns, css::sdbc::DataType::VARCHAR); + CPPUNIT_ASSERT(colVarchar != nullptr); + CPPUNIT_ASSERT(colVarchar->isNullable()); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(HsqlSchemaImportTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/dbaccess/qa/extras/macros-test.cxx b/dbaccess/qa/extras/macros-test.cxx new file mode 100644 index 000000000..dee5919c3 --- /dev/null +++ b/dbaccess/qa/extras/macros-test.cxx @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + */ + +#include <sal/config.h> +#include <test/unoapi_test.hxx> +#include <com/sun/star/util/XCloseable.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +#if !defined(MACOSX) && !defined(_WIN32) + +class DBAccessTest : public UnoApiTest +{ +public: + DBAccessTest(); + + void test(); + + CPPUNIT_TEST_SUITE(DBAccessTest); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); +}; + +DBAccessTest::DBAccessTest() + : UnoApiTest("/dbaccess/qa/extras/testdocuments") +{ +} + +void DBAccessTest::test() +{ + OUString aFileName; + createFileURL(u"testdb.odb", aFileName); + uno::Reference<lang::XComponent> xComponent = loadFromDesktop(aFileName); + uno::Reference<util::XCloseable> xDocCloseable(xComponent, UNO_QUERY_THROW); + xDocCloseable->close(false); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(DBAccessTest); + +#endif + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/dbaccess/qa/extras/nolib-save.cxx b/dbaccess/qa/extras/nolib-save.cxx new file mode 100644 index 000000000..59fee844f --- /dev/null +++ b/dbaccess/qa/extras/nolib-save.cxx @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + */ + +#include <sal/config.h> +#include <test/unoapi_test.hxx> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/document/XEmbeddedScripts.hpp> +#include <com/sun/star/script/XStorageBasedLibraryContainer.hpp> +#include <com/sun/star/script/XLibraryContainer.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> + +#include <vcl/svapp.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + + +class DialogSaveTest : public UnoApiTest +{ +public: + DialogSaveTest(); + + void test(); + + CPPUNIT_TEST_SUITE(DialogSaveTest); +// Should we disable this test on MOX and WNT? +// #if !defined(MACOSX) && !defined(_WIN32) + CPPUNIT_TEST(test); +// #endif + CPPUNIT_TEST_SUITE_END(); + +}; + + +DialogSaveTest::DialogSaveTest() + : UnoApiTest("/dbaccess/qa/extras/testdocuments") +{ +} + +void DialogSaveTest::test() +{ + const OUString aFileName(m_directories.getURLFromWorkdir(u"CppunitTest/testNolibSave.odb")); + { + uno::Reference< lang::XComponent > xComponent = loadFromDesktop(aFileName); + uno::Reference< frame::XStorable > xDocStorable(xComponent, UNO_QUERY_THROW); + uno::Reference< document::XEmbeddedScripts > xDocScr(xComponent, UNO_QUERY_THROW); + uno::Reference< script::XStorageBasedLibraryContainer > xStorBasLib(xDocScr->getBasicLibraries()); + CPPUNIT_ASSERT(xStorBasLib.is()); + uno::Reference< script::XLibraryContainer > xBasLib(xStorBasLib, UNO_QUERY_THROW); + uno::Reference< script::XStorageBasedLibraryContainer > xStorDlgLib(xDocScr->getDialogLibraries()); + CPPUNIT_ASSERT(xStorDlgLib.is()); + uno::Reference< script::XLibraryContainer > xDlgLib(xStorDlgLib, UNO_QUERY_THROW); + static const OUStringLiteral sStandard(u"Standard"); + xBasLib->loadLibrary(sStandard); + xDlgLib->loadLibrary(sStandard); + CPPUNIT_ASSERT(xBasLib->isLibraryLoaded(sStandard)); + CPPUNIT_ASSERT(xDlgLib->isLibraryLoaded(sStandard)); + + xBasLib->removeLibrary(sStandard); + xDlgLib->removeLibrary(sStandard); + + // uno::Reference< util::XModifiable > xDlgMod(xDlgLib, UNO_QUERY_THROW); + // xDlgMod->setModified(sal_True); + + // uno::Reference< util::XModifiable > xScrMod(xDocScr, UNO_QUERY_THROW); + // xScrMod->setModified(sal_True); + + // uno::Reference< util::XModifiable > xDocMod(xComponent, UNO_QUERY_THROW); + // std::cerr << "** Modified: " << static_cast<bool>(xDocMod->isModified()) << std::endl; + // xDocMod->setModified(sal_True); + // std::cerr << "** Modified: " << static_cast<bool>(xDocMod->isModified()) << std::endl; + // CPPUNIT_ASSERT(xDocMod->isModified()); + + // now save; the code path to exercise in this test is the "store to same location" + // do *not* change to store(As|To|URL)! + xDocStorable->store(); + + // close + uno::Reference< util::XCloseable > xDocCloseable(xComponent, UNO_QUERY_THROW); + xDocCloseable->close(false); + + // All our uno::References are (should?) be invalid now -> let them go out of scope + } + { + uno::Sequence<uno::Any> args{ uno::Any(aFileName) }; + Reference<container::XHierarchicalNameAccess> xHNA(getMultiServiceFactory()->createInstanceWithArguments("com.sun.star.packages.Package", args), UNO_QUERY_THROW); + CPPUNIT_ASSERT(!xHNA->hasByHierarchicalName("Basic/Standard")); + CPPUNIT_ASSERT(!xHNA->hasByHierarchicalName("Dialogs/Standard")); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(DialogSaveTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/dbaccess/qa/extras/rowsetclones.cxx b/dbaccess/qa/extras/rowsetclones.cxx new file mode 100644 index 000000000..d83d160ca --- /dev/null +++ b/dbaccess/qa/extras/rowsetclones.cxx @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + */ + +#include <sal/config.h> +#include <test/unoapi_test.hxx> +#include <com/sun/star/sdb/XOfficeDatabaseDocument.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/sdbcx/XRowLocate.hpp> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/sdb/XResultSetAccess.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdbcx; + +class RowSetClones : public UnoApiTest +{ +public: + RowSetClones(); + + void test(); + + CPPUNIT_TEST_SUITE(RowSetClones); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); + +}; + + +RowSetClones::RowSetClones() + : UnoApiTest("") +{ +} + +void RowSetClones::test() +{ + const OUString sFilePath(m_directories.getURLFromWorkdir(u"CppunitTest/RowSetClones.odb")); + + uno::Reference< lang::XComponent > xComponent (loadFromDesktop(sFilePath)); + uno::Reference< XOfficeDatabaseDocument > xDocument(xComponent, UNO_QUERY_THROW); + + uno::Reference< XDataSource > xDataSource = xDocument->getDataSource(); + CPPUNIT_ASSERT(xDataSource.is()); + + uno::Reference< XConnection > xConnection = xDataSource->getConnection("",""); + CPPUNIT_ASSERT(xConnection.is()); + + uno::Reference< XRowSet > xRowSet (getMultiServiceFactory()->createInstance("com.sun.star.sdb.RowSet" ), UNO_QUERY); + CPPUNIT_ASSERT(xRowSet.is()); + uno::Reference< XPropertySet > rowSetProperties ( xRowSet, UNO_QUERY ); + CPPUNIT_ASSERT(rowSetProperties.is()); + rowSetProperties->setPropertyValue("Command", Any(OUString("SELECT * FROM Assets ORDER BY AssetID"))); + rowSetProperties->setPropertyValue("CommandType", Any(CommandType::COMMAND)); + rowSetProperties->setPropertyValue("ActiveConnection", Any(xConnection)); + + xRowSet->execute(); + uno::Reference< XResultSet > xResultSet = xRowSet; + CPPUNIT_ASSERT(xResultSet.is()); + // always starts at BeforeFirst position + CPPUNIT_ASSERT(xResultSet->isBeforeFirst()); + CPPUNIT_ASSERT(xResultSet->next()); + CPPUNIT_ASSERT(xResultSet->isFirst()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xResultSet->getRow()); + + uno::Reference< XRow > xRow(xResultSet, UNO_QUERY); + CPPUNIT_ASSERT(xRow.is()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xRow->getInt(1)); + + uno::Reference< XResultSetAccess > xResultSetAccess(xResultSet, UNO_QUERY); + CPPUNIT_ASSERT(xResultSetAccess.is()); + uno::Reference< XResultSet > xResultSetClone = xResultSetAccess->createResultSet(); + CPPUNIT_ASSERT(xResultSetClone.is()); + + uno::Reference< XRow > xRowClone(xResultSetClone, UNO_QUERY); + CPPUNIT_ASSERT(xRowClone.is()); + + // the clone starts at same position as what it is cloned from, + // and does not move its source. + CPPUNIT_ASSERT(xResultSetClone->isFirst()); + CPPUNIT_ASSERT(xResultSet->isFirst()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xResultSet->getRow()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xResultSetClone->getRow()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xRow->getInt(1)); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xRowClone->getInt(1)); + + // if we move the source, the clone does not move + CPPUNIT_ASSERT(xResultSet->next()); + CPPUNIT_ASSERT(xResultSetClone->isFirst()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), xResultSet->getRow()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xResultSetClone->getRow()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), xRow->getInt(1)); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xRowClone->getInt(1)); + + CPPUNIT_ASSERT(xResultSet->last()); + CPPUNIT_ASSERT(xResultSet->isLast()); + CPPUNIT_ASSERT(xResultSetClone->isFirst()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xRowClone->getInt(1)); + + // and the other way round + CPPUNIT_ASSERT(xResultSet->first()); + CPPUNIT_ASSERT(xResultSetClone->next()); + CPPUNIT_ASSERT(xResultSet->isFirst()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), xResultSetClone->getRow()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), xRowClone->getInt(1)); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xRow->getInt(1)); + + CPPUNIT_ASSERT(xResultSetClone->last()); + CPPUNIT_ASSERT(xResultSetClone->isLast()); + CPPUNIT_ASSERT(xResultSet->isFirst()); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), xRow->getInt(1)); + + closeDocument(uno::Reference<lang::XComponent>(xDocument, uno::UNO_QUERY)); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(RowSetClones); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/dbaccess/qa/extras/testdocuments/RowSetClones.odb b/dbaccess/qa/extras/testdocuments/RowSetClones.odb Binary files differnew file mode 100644 index 000000000..91ed328ac --- /dev/null +++ b/dbaccess/qa/extras/testdocuments/RowSetClones.odb diff --git a/dbaccess/qa/extras/testdocuments/fdo84315.odb b/dbaccess/qa/extras/testdocuments/fdo84315.odb Binary files differnew file mode 100644 index 000000000..0513ff587 --- /dev/null +++ b/dbaccess/qa/extras/testdocuments/fdo84315.odb diff --git a/dbaccess/qa/extras/testdocuments/testDialogSave.odb b/dbaccess/qa/extras/testdocuments/testDialogSave.odb Binary files differnew file mode 100644 index 000000000..d72531253 --- /dev/null +++ b/dbaccess/qa/extras/testdocuments/testDialogSave.odb diff --git a/dbaccess/qa/extras/testdocuments/testdb.odb b/dbaccess/qa/extras/testdocuments/testdb.odb Binary files differnew file mode 100644 index 000000000..038e998e6 --- /dev/null +++ b/dbaccess/qa/extras/testdocuments/testdb.odb diff --git a/dbaccess/qa/python/fdo84315.py b/dbaccess/qa/python/fdo84315.py new file mode 100644 index 000000000..f58b16f51 --- /dev/null +++ b/dbaccess/qa/python/fdo84315.py @@ -0,0 +1,76 @@ +#! /usr/bin/env python +# +# 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/. +# + +import os +import unittest +from collections import deque +import unohelper +from org.libreoffice.unotest import UnoInProcess + +class Fdo84315(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls._uno = UnoInProcess() + cls._uno.setUp() + workdir = os.environ[ "WORKDIR_FOR_BUILD" ] + cls._xDoc = cls._uno.openDocFromAbsolutePath(workdir + "/CppunitTest/fdo84315.odb") + + @classmethod + def tearDownClass(cls): + cls._uno.tearDown() + + def __test_Query(self, column_name, expected_type, xResultset): + self.assertTrue(xResultset) + xMeta = xResultset.MetaData + self.assertEqual(xMeta.ColumnCount, 1) + self.assertEqual(xResultset.findColumn(column_name), 1) + self.assertEqual(xMeta.getColumnName(1), column_name) + self.assertEqual(xMeta.getColumnType(1), expected_type) + return xMeta + + def __test_ResultSetInteger(self, xResultset, expected_values): + while xResultset.next(): + self.assertEqual(xResultset.getInt(1), expected_values.popleft()) + self.assertEqual(len(expected_values), 0) + + def __test_ResultSetString(self, xResultset, expected_values): + while xResultset.next(): + self.assertEqual(xResultset.getString(1), expected_values.popleft()) + self.assertEqual(len(expected_values), 0) + + def test_fdo84315(self): + xDoc = self.__class__._xDoc + xDataSource = xDoc.DataSource + xCon = xDataSource.getConnection('','') + xStatement = xCon.createStatement() + + NUMERIC = 2 + VAR_CHAR = 12 + INTEGER = 4 + + xResultset = xStatement.executeQuery('SELECT "count" FROM "test_table"') + expected_values = deque([42, 4711]) + xMeta = self.__test_Query('count', NUMERIC, xResultset) + self.__test_ResultSetInteger(xResultset, expected_values) + + xResultset = xStatement.executeQuery('SELECT "name" FROM "test_table"') + expected_values = deque(['foo', 'bar']) + xMeta = self.__test_Query('name', VAR_CHAR, xResultset) + self.__test_ResultSetString(xResultset, expected_values) + + xResultset = xStatement.executeQuery('SELECT "id" FROM "test_table"') + expected_values = deque([0, 1]) + xMeta = self.__test_Query('id', INTEGER, xResultset) + self.__test_ResultSetInteger(xResultset, expected_values) + + xCon.dispose() + +if __name__ == '__main__': + unittest.main() diff --git a/dbaccess/qa/unit/data/dbaccess-dialogs-test.txt b/dbaccess/qa/unit/data/dbaccess-dialogs-test.txt new file mode 100644 index 000000000..3d0270889 --- /dev/null +++ b/dbaccess/qa/unit/data/dbaccess-dialogs-test.txt @@ -0,0 +1,111 @@ +# -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +# +# 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 contains all dialogs that the unit tests in the module +# will work on if it is in script mode. It will read one-by-one, +# try to open it and create a screenshot that will be saved in +# workdir/screenshots using the pattern of the ui-file name. +# +# Syntax: +# - empty lines are allowed +# - lines starting with '#' are treated as comment +# - all other lines should contain a *.ui filename in the same +# notation as in the dialog constructors (see code) + +# +# The 'known' dialogs which have a hard-coded representation +# in registerKnownDialogsByID/createDialogByID +# + +# No known dialogs in dbaccess for now + +# +# Dialogs without a hard-coded representation. These will +# be visualized using a fallback based on weld::Builder +# + +# currently deactivated, leads to problems and the test to not work +# This is typically a hint that these should be hard-coded in the +# test case since they need some document and model data to work +# dbaccess/ui/joindialog.ui <- not calling ORelationControl::lateInit for +# DlgQryJoin::m_xTableControl member OTableListBoxControl::m_pRC_Tables leaves its +# BrowseBox::mvCols empty, causing "implicit conversion from type 'int' of value -1 (32-bit, +# signed) to type 'sal_uInt16' (aka 'unsigned short') changed the value to 65535 (16-bit, +# unsigned)" when calling +# GetColumnId( static_cast<sal_uInt16>(mvCols.size()) - 1 ); +# in BrowseBox::AutoSizeLastColumn (svtools/source/brwbox/brwbox1.cxx) with Clang +# -fsanitize=implicit-signed-integer-truncation +# dbaccess/ui/relationdialog.ui <- not calling ORelationControl::lateInit for +# ORelationDialog::m_xTableControl member OTableListBoxControl::m_pRC_Tables leaves its +# BrowseBox::mvCols empty, causing "implicit conversion from type 'int' of value -1 (32-bit, +# signed) to type 'sal_uInt16' (aka 'unsigned short') changed the value to 65535 (16-bit, +# unsigned)" when calling +# GetColumnId( static_cast<sal_uInt16>(mvCols.size()) - 1 ); +# in BrowseBox::AutoSizeLastColumn (svtools/source/brwbox/brwbox1.cxx) with Clang +# -fsanitize=implicit-signed-integer-truncation + +dbaccess/ui/advancedsettingsdialog.ui +dbaccess/ui/admindialog.ui +dbaccess/ui/fielddialog.ui +dbaccess/ui/useradmindialog.ui +dbaccess/ui/mysqlnativesettings.ui +dbaccess/ui/textpage.ui +dbaccess/ui/applycolpage.ui +dbaccess/ui/copytablepage.ui +dbaccess/ui/namematchingpage.ui +dbaccess/ui/typeselectpage.ui +dbaccess/ui/specialsettingspage.ui +dbaccess/ui/generatedvaluespage.ui +dbaccess/ui/ldapconnectionpage.ui +dbaccess/ui/dbwizmysqlintropage.ui +dbaccess/ui/dbwizmysqlnativepage.ui +dbaccess/ui/specialjdbcconnectionpage.ui +dbaccess/ui/authentificationpage.ui +dbaccess/ui/finalpagewizard.ui +dbaccess/ui/tablesfilterpage.ui +dbaccess/ui/useradminpage.ui +dbaccess/ui/connectionpage.ui +dbaccess/ui/dbwizconnectionpage.ui +dbaccess/ui/dbwiztextpage.ui +dbaccess/ui/jdbcconnectionpage.ui +dbaccess/ui/dbwizspreadsheetpage.ui +dbaccess/ui/dbasepage.ui +dbaccess/ui/autocharsetpage.ui +dbaccess/ui/odbcpage.ui +dbaccess/ui/userdetailspage.ui +dbaccess/ui/autocharsetpage.ui +dbaccess/ui/generalspecialjdbcdetailspage.ui +dbaccess/ui/mysqlnativepage.ui +dbaccess/ui/ldappage.ui +dbaccess/ui/emptypage.ui +dbaccess/ui/generalpagedialog.ui +dbaccess/ui/generalpagewizard.ui +dbaccess/ui/collectionviewdialog.ui +dbaccess/ui/dbaseindexdialog.ui +dbaccess/ui/directsqldialog.ui +dbaccess/ui/savedialog.ui +dbaccess/ui/savedialog.ui +dbaccess/ui/rowheightdialog.ui +dbaccess/ui/colwidthdialog.ui +dbaccess/ui/choosedatasourcedialog.ui +dbaccess/ui/indexdesigndialog.ui +dbaccess/ui/parametersdialog.ui +dbaccess/ui/queryfilterdialog.ui +dbaccess/ui/sortdialog.ui +dbaccess/ui/querypropertiesdialog.ui +dbaccess/ui/sqlexception.ui +dbaccess/ui/textconnectionsettings.ui +dbaccess/ui/password.ui +dbaccess/ui/tablesfilterdialog.ui +dbaccess/ui/tablesjoindialog.ui +dbaccess/ui/savemodifieddialog.ui +dbaccess/ui/saveindexdialog.ui +dbaccess/ui/designsavemodifieddialog.ui +dbaccess/ui/tabledesignsavemodifieddialog.ui +dbaccess/ui/deleteallrowsdialog.ui diff --git a/dbaccess/qa/unit/data/firebird_empty.odb b/dbaccess/qa/unit/data/firebird_empty.odb Binary files differnew file mode 100644 index 000000000..9aabfd47b --- /dev/null +++ b/dbaccess/qa/unit/data/firebird_empty.odb diff --git a/dbaccess/qa/unit/data/firebird_integer_ods12.odb b/dbaccess/qa/unit/data/firebird_integer_ods12.odb Binary files differnew file mode 100644 index 000000000..642b038dd --- /dev/null +++ b/dbaccess/qa/unit/data/firebird_integer_ods12.odb diff --git a/dbaccess/qa/unit/data/hsqldb_empty.odb b/dbaccess/qa/unit/data/hsqldb_empty.odb Binary files differnew file mode 100644 index 000000000..087c26113 --- /dev/null +++ b/dbaccess/qa/unit/data/hsqldb_empty.odb diff --git a/dbaccess/qa/unit/data/hsqldb_migration_test.odb b/dbaccess/qa/unit/data/hsqldb_migration_test.odb Binary files differnew file mode 100644 index 000000000..99b6b5d9a --- /dev/null +++ b/dbaccess/qa/unit/data/hsqldb_migration_test.odb diff --git a/dbaccess/qa/unit/data/tdf119625.odb b/dbaccess/qa/unit/data/tdf119625.odb Binary files differnew file mode 100644 index 000000000..e7bd69d60 --- /dev/null +++ b/dbaccess/qa/unit/data/tdf119625.odb diff --git a/dbaccess/qa/unit/data/tdf126268.odb b/dbaccess/qa/unit/data/tdf126268.odb Binary files differnew file mode 100644 index 000000000..434a4238b --- /dev/null +++ b/dbaccess/qa/unit/data/tdf126268.odb diff --git a/dbaccess/qa/unit/data/tdf132924.odb b/dbaccess/qa/unit/data/tdf132924.odb Binary files differnew file mode 100644 index 000000000..8cee7bcba --- /dev/null +++ b/dbaccess/qa/unit/data/tdf132924.odb diff --git a/dbaccess/qa/unit/dbaccess-dialogs-test.cxx b/dbaccess/qa/unit/dbaccess-dialogs-test.cxx new file mode 100644 index 000000000..0551b3118 --- /dev/null +++ b/dbaccess/qa/unit/dbaccess-dialogs-test.cxx @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + */ + +#include <sal/config.h> +#include <test/screenshot_test.hxx> +#include <vcl/abstdlg.hxx> + +using namespace ::com::sun::star; + +/// Test opening a dialog in dbaccess +class DbaccessDialogsTest : public ScreenshotTest +{ +private: + /// helper method to populate KnownDialogs, called in setUp(). Needs to be + /// written and has to add entries to KnownDialogs + virtual void registerKnownDialogsByID(mapType& rKnownDialogs) override; + + /// dialog creation for known dialogs by ID. Has to be implemented for + /// each registered known dialog + virtual VclPtr<VclAbstractDialog> createDialogByID(sal_uInt32 nID) override; + +public: + DbaccessDialogsTest(); + + // try to open a dialog + void openAnyDialog(); + + CPPUNIT_TEST_SUITE(DbaccessDialogsTest); + CPPUNIT_TEST(openAnyDialog); + CPPUNIT_TEST_SUITE_END(); +}; + +DbaccessDialogsTest::DbaccessDialogsTest() {} + +void DbaccessDialogsTest::registerKnownDialogsByID(mapType& /*rKnownDialogs*/) +{ + // fill map of known dialogs +} + +VclPtr<VclAbstractDialog> DbaccessDialogsTest::createDialogByID(sal_uInt32 /*nID*/) +{ + return nullptr; +} + +void DbaccessDialogsTest::openAnyDialog() +{ + /// process input file containing the UXMLDescriptions of the dialogs to dump + processDialogBatchFile(u"dbaccess/qa/unit/data/dbaccess-dialogs-test.txt"); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(DbaccessDialogsTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/dbaccess/qa/unit/dbtest_base.cxx b/dbaccess/qa/unit/dbtest_base.cxx new file mode 100644 index 000000000..b5a3bd31c --- /dev/null +++ b/dbaccess/qa/unit/dbtest_base.cxx @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + */ + +#include <sal/config.h> + +#include <string_view> + +#include <cppunit/TestAssert.h> + +#include <test/unoapi_test.hxx> +#include <unotools/tempfile.hxx> +#include <com/sun/star/sdb/XOfficeDatabaseDocument.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdbc/XDataSource.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::uno; + +class DBTestBase + : public UnoApiTest +{ +public: + DBTestBase() : UnoApiTest("dbaccess/qa/unit/data") {}; + + utl::TempFile createTempCopy(std::u16string_view pathname); + + uno::Reference< XOfficeDatabaseDocument > + getDocumentForFileName(std::u16string_view sFileName); + + uno::Reference<XOfficeDatabaseDocument> getDocumentForUrl(OUString const & url); + + uno::Reference< XConnection > + getConnectionForDocument( + uno::Reference< XOfficeDatabaseDocument > const & xDocument); +}; + +utl::TempFile DBTestBase::createTempCopy(std::u16string_view pathname) { + OUString url; + createFileURL(pathname, url); + utl::TempFile tmp; + tmp.EnableKillingFile(); + auto const e = osl::File::copy(url, tmp.GetURL()); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + (OString( + "<" + OUStringToOString(url, RTL_TEXTENCODING_UTF8) + "> -> <" + + OUStringToOString(tmp.GetURL(), RTL_TEXTENCODING_UTF8) + ">") + .getStr()), + osl::FileBase::E_None, e); + return tmp; +} + +uno::Reference< XOfficeDatabaseDocument > + DBTestBase::getDocumentForFileName(std::u16string_view sFileName) +{ + OUString sFilePath; + createFileURL(sFileName, sFilePath); + return getDocumentForUrl(sFilePath); +} + +uno::Reference<XOfficeDatabaseDocument> DBTestBase::getDocumentForUrl(OUString const & url) { + uno::Reference< lang::XComponent > xComponent (loadFromDesktop(url)); + uno::Reference< XOfficeDatabaseDocument > xDocument(xComponent, UNO_QUERY_THROW); + return xDocument; +} + +uno::Reference< XConnection > DBTestBase::getConnectionForDocument( + uno::Reference< XOfficeDatabaseDocument > const & xDocument) +{ + uno::Reference< XDataSource > xDataSource = xDocument->getDataSource(); + CPPUNIT_ASSERT(xDataSource.is()); + + uno::Reference< XConnection > xConnection = xDataSource->getConnection("",""); + CPPUNIT_ASSERT(xConnection.is()); + + return xConnection; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/dbaccess/qa/unit/embeddeddb_performancetest.cxx b/dbaccess/qa/unit/embeddeddb_performancetest.cxx new file mode 100644 index 000000000..17eaa52ae --- /dev/null +++ b/dbaccess/qa/unit/embeddeddb_performancetest.cxx @@ -0,0 +1,358 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + */ + +#include "dbtest_base.cxx" + +#include <memory> +#include <osl/process.h> +#include <osl/time.h> +#include <rtl/ustrbuf.hxx> +#include <tools/stream.hxx> +#include <unotools/tempfile.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/sdb/XOfficeDatabaseDocument.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdbc/XParameters.hpp> +#include <com/sun/star/sdbc/XPreparedStatement.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/sdbc/XStatement.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::uno; + +static void normaliseTimeValue(TimeValue* pVal) +{ + pVal->Seconds += pVal->Nanosec / 1000000000; + pVal->Nanosec %= 1000000000; +} + +static void getTimeDifference(const TimeValue* pTimeStart, + const TimeValue* pTimeEnd, + TimeValue* pTimeDifference) +{ + // We add 1 second to the nanoseconds to ensure that we get a positive number + // We have to normalise anyway so this doesn't cause any harm. + // (Seconds/Nanosec are both unsigned) + pTimeDifference->Seconds = pTimeEnd->Seconds - pTimeStart->Seconds - 1; + pTimeDifference->Nanosec = 1000000000 + pTimeEnd->Nanosec - pTimeStart->Nanosec; + normaliseTimeValue(pTimeDifference); +} + +static OUString getPrintableTimeValue(const TimeValue* pTimeValue) +{ + return OUString::number( + (sal_uInt64(pTimeValue->Seconds) * SAL_CONST_UINT64(1000000000) + + sal_uInt64(pTimeValue->Nanosec))/ 1000000 + ); +} + +/* + * The recommended way to run this test is: + * 'SAL_LOG="" DBA_PERFTEST=YES make CppunitTest_dbaccess_embeddeddb_performancetest' + * This blocks the unnecessary exception output and show only the performance data. + * + * You also need to create the file dbaccess/qa/unit/data/wordlist, this list cannot + * contain any unescaped apostrophes (since the words are used directly to assemble + * sql statement), apostrophes are escaped using a double apostrophe, i.e. ''. + * one easy way of generating a list is using: + * 'for WORD in $(aspell dump master); do echo ${WORD//\'/\'\'}; done > dbaccess/qa/unit/data/wordlist' + * + * Note that wordlist cannot have more than 220580 lines, this is due to a hard + * limit in our hsqldb version. + * + * Also note that this unit test "fails" when doing performance testing, this is + * since by default unit test output is hidden, and thus there is no way of + * reading the results. + */ +class EmbeddedDBPerformanceTest + : public DBTestBase +{ +private: + static constexpr OUStringLiteral our_sEnableTestEnvVar = u"DBA_PERFTEST"; + + + // We store the results and print them at the end due to the amount of warning + // noise present which otherwise obscures the results. + OUStringBuffer m_aOutputBuffer; + + void printTimes(const TimeValue* pTime1, const TimeValue* pTime2, const TimeValue* pTime3); + + void doPerformanceTestOnODB(const OUString& rDriverURL, + std::u16string_view rDBName, + const bool bUsePreparedStatement); + + void setupTestTable(uno::Reference< XConnection > const & xConnection); + + SvFileStream *getWordListStream(); + + // Individual Tests + void performPreparedStatementInsertTest( + uno::Reference< XConnection > const & xConnection, + std::u16string_view rDBName); + void performStatementInsertTest( + uno::Reference< XConnection > const & xConnection, + std::u16string_view rDBName); + void performReadTest( + uno::Reference< XConnection > const & xConnection, + std::u16string_view rDBName); + + // Perform all tests on a given DB. + void testFirebird(); + void testHSQLDB(); + +public: + void testPerformance(); + + CPPUNIT_TEST_SUITE(EmbeddedDBPerformanceTest); + CPPUNIT_TEST(testPerformance); + CPPUNIT_TEST_SUITE_END(); +}; + +SvFileStream* EmbeddedDBPerformanceTest::getWordListStream() +{ + OUString wlPath; + createFileURL(u"wordlist", wlPath); + return new SvFileStream(wlPath, StreamMode::READ); +} + +void EmbeddedDBPerformanceTest::printTimes( + const TimeValue* pTime1, + const TimeValue* pTime2, + const TimeValue* pTime3) +{ + m_aOutputBuffer.append( + getPrintableTimeValue(pTime1) + "\t" + + getPrintableTimeValue(pTime2) + "\t" + + getPrintableTimeValue(pTime3) + "\t" + "\n"); +} + +// TODO: we probably should create a document from scratch instead? + +void EmbeddedDBPerformanceTest::testPerformance() +{ + OUString sEnabled; + osl_getEnvironment(OUString(our_sEnableTestEnvVar).pData, &sEnabled.pData); + + if (sEnabled.isEmpty()) + return; + + m_aOutputBuffer.append("---------------------\n"); + testFirebird(); + m_aOutputBuffer.append("---------------------\n"); + testHSQLDB(); + m_aOutputBuffer.append("---------------------\n"); + + fprintf(stdout, "Performance Test Results:\n"); + fprintf(stdout, "%s", + OUStringToOString(m_aOutputBuffer.makeStringAndClear(), + RTL_TEXTENCODING_UTF8) + .getStr() + ); + + // We want the results printed, but unit test output is only printed on failure + // Hence we deliberately fail the test. + CPPUNIT_ASSERT(false); +} + +void EmbeddedDBPerformanceTest::testFirebird() +{ + + m_aOutputBuffer.append("Standard Insert\n"); + doPerformanceTestOnODB("sdbc:embedded:firebird", u"Firebird", false); + m_aOutputBuffer.append("PreparedStatement Insert\n"); + doPerformanceTestOnODB("sdbc:embedded:firebird", u"Firebird", true); +} + +void EmbeddedDBPerformanceTest::testHSQLDB() +{ + m_aOutputBuffer.append("Standard Insert\n"); + doPerformanceTestOnODB("sdbc:embedded:hsqldb", u"HSQLDB", false); + m_aOutputBuffer.append("PreparedStatement Insert\n"); + doPerformanceTestOnODB("sdbc:embedded:hsqldb", u"HSQLDB", true); +} + +/** + * Use an existing .odb to do performance tests on. The database cannot have + * a table of the name PFTESTTABLE. + */ +void EmbeddedDBPerformanceTest::doPerformanceTestOnODB( + const OUString& rDriverURL, + std::u16string_view rDBName, + const bool bUsePreparedStatement) +{ + ::utl::TempFile aFile; + aFile.EnableKillingFile(); + + { + uno::Reference< XOfficeDatabaseDocument > xDocument( + m_xSFactory->createInstance("com.sun.star.sdb.OfficeDatabaseDocument"), + UNO_QUERY_THROW); + uno::Reference< XStorable > xStorable(xDocument, UNO_QUERY_THROW); + + uno::Reference< XDataSource > xDataSource = xDocument->getDataSource(); + uno::Reference< XPropertySet > xPropertySet(xDataSource, UNO_QUERY_THROW); + xPropertySet->setPropertyValue("URL", Any(rDriverURL)); + + xStorable->storeAsURL(aFile.GetURL(), uno::Sequence< beans::PropertyValue >()); + } + + uno::Reference< XOfficeDatabaseDocument > xDocument( + loadFromDesktop(aFile.GetURL()), UNO_QUERY_THROW); + + uno::Reference< XConnection > xConnection = + getConnectionForDocument(xDocument); + + setupTestTable(xConnection); + + if (bUsePreparedStatement) + performPreparedStatementInsertTest(xConnection, rDBName); + else + performStatementInsertTest(xConnection, rDBName); + + performReadTest(xConnection, rDBName); +} + +void EmbeddedDBPerformanceTest::setupTestTable( + uno::Reference< XConnection > const & xConnection) +{ + uno::Reference< XStatement > xStatement = xConnection->createStatement(); + + // Although not strictly necessary we use quoted identifiers to reflect + // the fact that Base always uses quoted identifiers. + xStatement->execute( + "CREATE TABLE \"PFTESTTABLE\" ( \"ID\" INTEGER NOT NULL PRIMARY KEY " + ", \"STRINGCOLUMNA\" VARCHAR (50) " + ")"); + + xConnection->commit(); +} + +void EmbeddedDBPerformanceTest::performPreparedStatementInsertTest( + uno::Reference< XConnection > const & xConnection, + std::u16string_view rDBName) +{ + uno::Reference< XPreparedStatement > xPreparedStatement = + xConnection->prepareStatement( + "INSERT INTO \"PFTESTTABLE\" ( \"ID\", " + "\"STRINGCOLUMNA\" " + ") VALUES ( ?, ? )" + ); + + uno::Reference< XParameters > xParameters(xPreparedStatement, UNO_QUERY_THROW); + + std::unique_ptr< SvFileStream > pFile(getWordListStream()); + + OUString aWord; + sal_Int32 aID = 0; + + TimeValue aStart, aMiddle, aEnd; + osl_getSystemTime(&aStart); + + while (pFile->ReadByteStringLine(aWord, RTL_TEXTENCODING_UTF8)) + { + xParameters->setInt(1, aID++); + xParameters->setString(2, aWord); + xPreparedStatement->execute(); + } + osl_getSystemTime(&aMiddle); + xConnection->commit(); + osl_getSystemTime(&aEnd); + + + TimeValue aTimeInsert, aTimeCommit, aTimeTotal; + getTimeDifference(&aStart, &aMiddle, &aTimeInsert); + getTimeDifference(&aMiddle, &aEnd, &aTimeCommit); + getTimeDifference(&aStart, &aEnd, &aTimeTotal); + m_aOutputBuffer.append(OUString::Concat("Insert: ") + rDBName + "\n"); + printTimes(&aTimeInsert, &aTimeCommit, &aTimeTotal); + + pFile->Close(); +} + +void EmbeddedDBPerformanceTest::performStatementInsertTest( + uno::Reference< XConnection > const & xConnection, + std::u16string_view rDBName) +{ + uno::Reference< XStatement > xStatement = + xConnection->createStatement(); + + std::unique_ptr< SvFileStream > pFile(getWordListStream()); + + OUString aWord; + sal_Int32 aID = 0; + + TimeValue aStart, aMiddle, aEnd; + osl_getSystemTime(&aStart); + + while (pFile->ReadByteStringLine(aWord, RTL_TEXTENCODING_UTF8)) + { + xStatement->execute( + "INSERT INTO \"PFTESTTABLE\" ( \"ID\", " + "\"STRINGCOLUMNA\" " + ") VALUES ( " + + OUString::number(aID++) + ", '" + aWord + "' )" + ); + } + osl_getSystemTime(&aMiddle); + xConnection->commit(); + osl_getSystemTime(&aEnd); + + TimeValue aTimeInsert, aTimeCommit, aTimeTotal; + getTimeDifference(&aStart, &aMiddle, &aTimeInsert); + getTimeDifference(&aMiddle, &aEnd, &aTimeCommit); + getTimeDifference(&aStart, &aEnd, &aTimeTotal); + m_aOutputBuffer.append(OUString::Concat("Insert: ") + rDBName + "\n"); + printTimes(&aTimeInsert, &aTimeCommit, &aTimeTotal); + + pFile->Close(); +} + +void EmbeddedDBPerformanceTest::performReadTest( + uno::Reference< XConnection > const & xConnection, + std::u16string_view rDBName) +{ + uno::Reference< XStatement > xStatement = xConnection->createStatement(); + + TimeValue aStart, aMiddle, aEnd; + osl_getSystemTime(&aStart); + + uno::Reference< XResultSet > xResults = xStatement->executeQuery("SELECT * FROM PFTESTTABLE"); + + osl_getSystemTime(&aMiddle); + + uno::Reference< XRow > xRow(xResults, UNO_QUERY_THROW); + + while (xResults->next()) + { + xRow->getString(2); + } + osl_getSystemTime(&aEnd); + + TimeValue aTimeSelect, aTimeIterate, aTimeTotal; + getTimeDifference(&aStart, &aMiddle, &aTimeSelect); + getTimeDifference(&aMiddle, &aEnd, &aTimeIterate); + getTimeDifference(&aStart, &aEnd, &aTimeTotal); + m_aOutputBuffer.append(OUString::Concat("Read from: ") + rDBName + "\n"); + printTimes(&aTimeSelect, &aTimeIterate, &aTimeTotal); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(EmbeddedDBPerformanceTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/dbaccess/qa/unit/firebird.cxx b/dbaccess/qa/unit/firebird.cxx new file mode 100644 index 000000000..7a8143810 --- /dev/null +++ b/dbaccess/qa/unit/firebird.cxx @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + */ + +#include "dbtest_base.cxx" + +#include <com/sun/star/sdb/XOfficeDatabaseDocument.hpp> +#include <com/sun/star/sdbc/XColumnLocate.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/sdbc/XStatement.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::uno; + +class FirebirdTest + : public DBTestBase +{ +public: + void testEmptyDBConnection(); + void testIntegerDatabase(); + void testTdf132924(); + + CPPUNIT_TEST_SUITE(FirebirdTest); + CPPUNIT_TEST(testEmptyDBConnection); + CPPUNIT_TEST(testIntegerDatabase); + CPPUNIT_TEST(testTdf132924); + CPPUNIT_TEST_SUITE_END(); +}; + +/** + * Test the loading of an "empty" file, i.e. the embedded database has not yet + * been initialised (as occurs when a new .odb is created and opened by base). + */ +void FirebirdTest::testEmptyDBConnection() +{ + auto const tmp = createTempCopy(u"firebird_empty.odb"); + uno::Reference< XOfficeDatabaseDocument > xDocument = + getDocumentForUrl(tmp.GetURL()); + + getConnectionForDocument(xDocument); + + closeDocument(uno::Reference<lang::XComponent>(xDocument, uno::UNO_QUERY)); +} + +/** + * Test reading of integers from a known .odb to verify that the data + * can still be read on all systems. + */ +void FirebirdTest::testIntegerDatabase() +{ + uno::Reference< XOfficeDatabaseDocument > xDocument = + getDocumentForFileName(u"firebird_integer_ods12.odb"); + + uno::Reference< XConnection > xConnection = + getConnectionForDocument(xDocument); + + uno::Reference< XStatement > xStatement = xConnection->createStatement(); + CPPUNIT_ASSERT(xStatement.is()); + + uno::Reference< XResultSet > xResultSet = xStatement->executeQuery( + "SELECT * FROM TESTTABLE"); + CPPUNIT_ASSERT(xResultSet.is()); + CPPUNIT_ASSERT(xResultSet->next()); + + uno::Reference< XRow > xRow(xResultSet, UNO_QUERY); + CPPUNIT_ASSERT(xRow.is()); + uno::Reference< XColumnLocate > xColumnLocate(xRow, UNO_QUERY); + CPPUNIT_ASSERT(xColumnLocate.is()); + + CPPUNIT_ASSERT_EQUAL(sal_Int16(-30000), + xRow->getShort(xColumnLocate->findColumn("_SMALLINT"))); + CPPUNIT_ASSERT_EQUAL(sal_Int32(-2100000000), + xRow->getInt(xColumnLocate->findColumn("_INT"))); + CPPUNIT_ASSERT_EQUAL(SAL_CONST_INT64(-9000000000000000000), + xRow->getLong(xColumnLocate->findColumn("_BIGINT"))); + CPPUNIT_ASSERT_EQUAL(OUString("5"), + xRow->getString(xColumnLocate->findColumn("_CHAR"))); + CPPUNIT_ASSERT_EQUAL(OUString("5"), + xRow->getString(xColumnLocate->findColumn("_VARCHAR"))); + + CPPUNIT_ASSERT(!xResultSet->next()); // Should only be one row + + closeDocument(uno::Reference<lang::XComponent>(xDocument, uno::UNO_QUERY)); +} + +void FirebirdTest::testTdf132924() +{ + uno::Reference<XOfficeDatabaseDocument> xDocument = getDocumentForFileName(u"tdf132924.odb"); + uno::Reference<XConnection> xConnection = getConnectionForDocument(xDocument); + + uno::Reference<XStatement> xStatement = xConnection->createStatement(); + CPPUNIT_ASSERT(xStatement.is()); + + uno::Reference<XResultSet> xResultSet = xStatement->executeQuery("SELECT * FROM AliasTest"); + CPPUNIT_ASSERT(xResultSet.is()); + CPPUNIT_ASSERT(xResultSet->next()); + + uno::Reference<XRow> xRow(xResultSet, UNO_QUERY); + CPPUNIT_ASSERT(xRow.is()); + uno::Reference<XColumnLocate> xColumnLocate(xRow, UNO_QUERY); + CPPUNIT_ASSERT(xColumnLocate.is()); + + // Without the fix in place, this test would have failed with: + // - Expected: 1 + // - Actual : The column name 'TestId' is not valid + CPPUNIT_ASSERT_EQUAL(sal_Int16(1), xRow->getShort(xColumnLocate->findColumn("TestId"))); + CPPUNIT_ASSERT_EQUAL(OUString("TestName"), xRow->getString(xColumnLocate->findColumn("TestName"))); + + closeDocument(uno::Reference<lang::XComponent>(xDocument, uno::UNO_QUERY)); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(FirebirdTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/dbaccess/qa/unit/hsql_binary_import.cxx b/dbaccess/qa/unit/hsql_binary_import.cxx new file mode 100644 index 000000000..4eac0cdc4 --- /dev/null +++ b/dbaccess/qa/unit/hsql_binary_import.cxx @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + */ + +#include "dbtest_base.cxx" + +#include <osl/process.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <com/sun/star/sdbc/XRow.hpp> +#include <cppunit/extensions/HelperMacros.h> +#include <officecfg/Office/Common.hxx> + +class HsqlBinaryImportTest : public DBTestBase +{ +public: + void testBinaryImport(); + + virtual void setUp() override; + + CPPUNIT_TEST_SUITE(HsqlBinaryImportTest); + + CPPUNIT_TEST(testBinaryImport); + + CPPUNIT_TEST_SUITE_END(); +}; + +void HsqlBinaryImportTest::setUp() +{ + DBTestBase::setUp(); + osl_setEnvironment(OUString{ "DBACCESS_HSQL_MIGRATION" }.pData, OUString{ "1" }.pData); +} + +void HsqlBinaryImportTest::testBinaryImport() +{ + bool oldValue = officecfg::Office::Common::Misc::ExperimentalMode::get(); + { + std::shared_ptr<comphelper::ConfigurationChanges> xChanges( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Misc::ExperimentalMode::set(true, xChanges); + xChanges->commit(); + } + + // the migration requires the file to be writable + utl::TempFile const temp(createTempCopy(u"hsqldb_migration_test.odb")); + uno::Reference<XOfficeDatabaseDocument> const xDocument = getDocumentForUrl(temp.GetURL()); + + uno::Reference<XConnection> xConnection = getConnectionForDocument(xDocument); + // at this point migration is already done + + uno::Reference<XStatement> statement = xConnection->createStatement(); + + uno::Reference<XResultSet> xRes + = statement->executeQuery("SELECT \"ID\", \"Power_value\", \"Power_name\", \"Retired\", " + "\"Birth_date\" FROM \"TestTable\" ORDER BY \"ID\""); + uno::Reference<XRow> xRow(xRes, UNO_QUERY_THROW); + + // assert first row + CPPUNIT_ASSERT(xRes->next()); + constexpr sal_Int16 idExpected = 1; + CPPUNIT_ASSERT_EQUAL(idExpected, xRow->getShort(1)); + CPPUNIT_ASSERT_EQUAL(OUString{ "45.32" }, xRow->getString(2)); // numeric + CPPUNIT_ASSERT_EQUAL(OUString{ "laser eye" }, xRow->getString(3)); // varchar + CPPUNIT_ASSERT(xRow->getBoolean(4)); // boolean + + css::util::Date date = xRow->getDate(5); + + CPPUNIT_ASSERT_EQUAL(sal_uInt16{ 15 }, date.Day); + CPPUNIT_ASSERT_EQUAL(sal_uInt16{ 1 }, date.Month); + CPPUNIT_ASSERT_EQUAL(sal_Int16{ 1996 }, date.Year); + + // assert second row + CPPUNIT_ASSERT(xRes->next()); + constexpr sal_Int16 secondIdExpected = 2; + CPPUNIT_ASSERT_EQUAL(secondIdExpected, xRow->getShort(1)); // ID + CPPUNIT_ASSERT_EQUAL(OUString{ "54.12" }, xRow->getString(2)); // numeric + CPPUNIT_ASSERT_EQUAL(OUString{ "telekinesis" }, xRow->getString(3)); // varchar + CPPUNIT_ASSERT(!xRow->getBoolean(4)); // boolean + + date = xRow->getDate(5); + CPPUNIT_ASSERT_EQUAL(sal_uInt16{ 26 }, date.Day); + CPPUNIT_ASSERT_EQUAL(sal_uInt16{ 2 }, date.Month); + CPPUNIT_ASSERT_EQUAL(sal_Int16{ 1998 }, date.Year); + + closeDocument(uno::Reference<lang::XComponent>(xDocument, uno::UNO_QUERY)); + if (!oldValue) + { + std::shared_ptr<comphelper::ConfigurationChanges> xChanges( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Misc::ExperimentalMode::set(false, xChanges); + xChanges->commit(); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(HsqlBinaryImportTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/dbaccess/qa/unit/hsqldb.cxx b/dbaccess/qa/unit/hsqldb.cxx new file mode 100644 index 000000000..5ee011af2 --- /dev/null +++ b/dbaccess/qa/unit/hsqldb.cxx @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + */ + +#include "dbtest_base.cxx" + +#include <com/sun/star/sdb/XOfficeDatabaseDocument.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::uno; + +class HSQLDBTest : public DBTestBase +{ +public: + void testEmptyDBConnection(); + + CPPUNIT_TEST_SUITE(HSQLDBTest); + CPPUNIT_TEST(testEmptyDBConnection); + CPPUNIT_TEST_SUITE_END(); +}; + +/** + * Test the loading of an "empty" file, i.e. the embedded database has not yet + * been initialised (as occurs when a new .odb is created and opened by base). + */ +void HSQLDBTest::testEmptyDBConnection() +{ + auto const file = createTempCopy(u"hsqldb_empty.odb"); + uno::Reference<XOfficeDatabaseDocument> xDocument = getDocumentForUrl(file.GetURL()); + + getConnectionForDocument(xDocument); + + css::uno::Reference<css::lang::XComponent>(xDocument, css::uno::UNO_QUERY_THROW)->dispose(); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(HSQLDBTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/dbaccess/qa/unit/tdf119625.cxx b/dbaccess/qa/unit/tdf119625.cxx new file mode 100644 index 000000000..22fb58493 --- /dev/null +++ b/dbaccess/qa/unit/tdf119625.cxx @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + */ + +#include "dbtest_base.cxx" + +#include <osl/process.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <com/sun/star/sdbc/XRow.hpp> +#include <cppunit/extensions/HelperMacros.h> +#include <com/sun/star/util/Time.hpp> +#include <officecfg/Office/Common.hxx> + +class Tdf119625Test : public DBTestBase +{ +public: + void testTime(); + + virtual void setUp() override; + + CPPUNIT_TEST_SUITE(Tdf119625Test); + + CPPUNIT_TEST(testTime); + + CPPUNIT_TEST_SUITE_END(); +}; + +void Tdf119625Test::setUp() +{ + DBTestBase::setUp(); + osl_setEnvironment(OUString{ "DBACCESS_HSQL_MIGRATION" }.pData, OUString{ "1" }.pData); +} + +namespace +{ +struct expect_t +{ + sal_Int16 id; + sal_Int16 h, m, s; +}; +} + +/* The values here assume that our results are in UTC. However, + tdf#119675 "Firebird: Migration: User dialog to set treatment of + datetime and time values during migration" is going to change the + final result of migration. If that change is implemented below + the level we are testing, this test will have to allow for or set + the destination timezone. + */ +const expect_t expect[] = { { 0, 15, 10, 10 }, { 1, 23, 30, 30 }, { 2, 5, 0, 0 }, { 3, 4, 30, 0 }, + { 4, 3, 15, 10 }, { 5, 5, 0, 0 }, { 6, 3, 22, 22 } }; + +void Tdf119625Test::testTime() +{ + bool oldValue = officecfg::Office::Common::Misc::ExperimentalMode::get(); + { + std::shared_ptr<comphelper::ConfigurationChanges> xChanges( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Misc::ExperimentalMode::set(true, xChanges); + xChanges->commit(); + } + + // the migration requires the file to be writable + utl::TempFile const temp(createTempCopy(u"tdf119625.odb")); + uno::Reference<XOfficeDatabaseDocument> const xDocument = getDocumentForUrl(temp.GetURL()); + + uno::Reference<XConnection> xConnection = getConnectionForDocument(xDocument); + // at this point migration is already done + /* In the presence of tdf#119625, terminal already has messages + + *value exceeds the range for a valid time + caused by + 'isc_dsql_execute' + + warn:dbaccess:22435:22435:dbaccess/source/filter/hsqldb/hsqlimport.cxx:373: Error during migration + + In this case, we do not expect anything good from the following + code, but I (tje, 2018-09-04) do not know how to detect this + situation. In particular, the migration has been observed to + create the destination table (but truncated after the first + row), and xConnection.is() returns true. + */ + + // select basically everything from the .odb + uno::Reference<XStatement> statement = xConnection->createStatement(); + + uno::Reference<XResultSet> xRes = statement->executeQuery(" SELECT id, tst_dt, tst_d, tst_t " + " FROM tst_data " + "ORDER BY id"); + uno::Reference<XRow> xRow(xRes, UNO_QUERY_THROW); + + // check result + for (auto& e : expect) + { + CPPUNIT_ASSERT(xRes->next()); + CPPUNIT_ASSERT_EQUAL(xRow->getShort(1), e.id); + auto time_got = xRow->getTime(4); + auto time_expected = com::sun::star::util::Time(0, e.s, e.m, e.h, false); + auto equal_times = time_got == time_expected; + CPPUNIT_ASSERT(equal_times); + } + CPPUNIT_ASSERT(!xRes->next()); + + closeDocument(uno::Reference<lang::XComponent>(xDocument, uno::UNO_QUERY)); + if (!oldValue) + { + std::shared_ptr<comphelper::ConfigurationChanges> xChanges( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Misc::ExperimentalMode::set(false, xChanges); + xChanges->commit(); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Tdf119625Test); + +CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/dbaccess/qa/unit/tdf126268.cxx b/dbaccess/qa/unit/tdf126268.cxx new file mode 100644 index 000000000..a4ed9dc3b --- /dev/null +++ b/dbaccess/qa/unit/tdf126268.cxx @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * 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/. + */ + +#include "dbtest_base.cxx" + +#include <osl/process.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <com/sun/star/sdbc/XRow.hpp> +#include <cppunit/extensions/HelperMacros.h> +#include <officecfg/Office/Common.hxx> + +class Tdf126268Test : public DBTestBase +{ +public: + void testNumbers(); + + virtual void setUp() override; + + CPPUNIT_TEST_SUITE(Tdf126268Test); + + CPPUNIT_TEST(testNumbers); + + CPPUNIT_TEST_SUITE_END(); +}; + +void Tdf126268Test::setUp() +{ + DBTestBase::setUp(); + osl_setEnvironment(OUString{ "DBACCESS_HSQL_MIGRATION" }.pData, OUString{ "1" }.pData); +} + +namespace +{ +struct expect_t +{ + sal_Int16 id; + OUString number; +}; +} + +const expect_t expect[] = { + { 1, "0.00" }, { 2, "25.00" }, { 3, "26.00" }, { 4, "30.4" }, { 5, "45.8" }, + { 6, "-25.00" }, { 7, "-26.00" }, { 8, "-30.4" }, { 9, "-45.8" }, +}; + +void Tdf126268Test::testNumbers() +{ + bool oldValue = officecfg::Office::Common::Misc::ExperimentalMode::get(); + { + std::shared_ptr<comphelper::ConfigurationChanges> xChanges( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Misc::ExperimentalMode::set(true, xChanges); + xChanges->commit(); + } + + // the migration requires the file to be writable + utl::TempFile const temp(createTempCopy(u"tdf126268.odb")); + uno::Reference<XOfficeDatabaseDocument> const xDocument = getDocumentForUrl(temp.GetURL()); + + uno::Reference<XConnection> xConnection = getConnectionForDocument(xDocument); + + // select basically everything from the .odb + uno::Reference<XStatement> statement = xConnection->createStatement(); + + uno::Reference<XResultSet> xRes + = statement->executeQuery("SELECT ID, Column1, Column2 FROM tableTest ORDER BY ID"); + uno::Reference<XRow> xRow(xRes, UNO_QUERY_THROW); + + // check result + for (auto& e : expect) + { + CPPUNIT_ASSERT(xRes->next()); + CPPUNIT_ASSERT_EQUAL(e.id, xRow->getShort(1)); + CPPUNIT_ASSERT_EQUAL(e.number, xRow->getString(2)); //decimal + CPPUNIT_ASSERT_EQUAL(e.number, xRow->getString(3)); //numeric + } + CPPUNIT_ASSERT(!xRes->next()); + + closeDocument(uno::Reference<lang::XComponent>(xDocument, uno::UNO_QUERY)); + if (!oldValue) + { + std::shared_ptr<comphelper::ConfigurationChanges> xChanges( + comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Misc::ExperimentalMode::set(false, xChanges); + xChanges->commit(); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Tdf126268Test); + +CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/dbaccess/qa/unoapi/dbaccess.props b/dbaccess/qa/unoapi/dbaccess.props new file mode 100644 index 000000000..afad9f038 --- /dev/null +++ b/dbaccess/qa/unoapi/dbaccess.props @@ -0,0 +1,24 @@ +# +# 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 . +# + +#properties needed for dbbaccess + +jdbc.url=mysql://unoapi:3306/testDB +jdbc.user=unoapi +jdbc.password=unoapi +mysql.url=sdbc:mysql:jdbc:unoapi:3306/TestDB diff --git a/dbaccess/qa/unoapi/dbaccess.sce b/dbaccess/qa/unoapi/dbaccess.sce new file mode 100644 index 000000000..13fa966ab --- /dev/null +++ b/dbaccess/qa/unoapi/dbaccess.sce @@ -0,0 +1,32 @@ +# +# 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 . +# +#i84113 -o dbaccess.ConnectionLineAccessibility +-o dbaccess.DBContentLoader +#i84114 -o dbaccess.JoinViewAccessibility +-o dbaccess.OCommandDefinition +-o dbaccess.ODatabaseContext +-o dbaccess.ODatabaseSource +-o dbaccess.ODatasourceAdministrationDialog +#i98007 -o dbaccess.ODatasourceBrowser +-o dbaccess.OInteractionHandler +#i84116 -o dbaccess.OQueryDesign +-o dbaccess.ORowSet +-o dbaccess.OSQLMessageDialog +-o dbaccess.OSingleSelectQueryComposer +#i95611 -o dbaccess.SbaXGridControl +#i84128 -o dbaccess.TableWindowAccessibility diff --git a/dbaccess/qa/unoapi/knownissues.xcl b/dbaccess/qa/unoapi/knownissues.xcl new file mode 100644 index 000000000..8c635d097 --- /dev/null +++ b/dbaccess/qa/unoapi/knownissues.xcl @@ -0,0 +1,80 @@ +# +# 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 . +# + +### 114044 ### +dbaccess.ORowSet::com::sun::star::sdbc::XWarningsSupplier + +### i38209 ### +dbaccess.ODatasourceAdministrationDialog::com::sun::star::sdb::DatasourceAdministrationDialog + +### i55579 ### +dbaccess.OQueryDesign::com::sun::star::frame::XController + +### i84113 ### +dbaccess.ConnectionLineAccessibility +# -> disabled in dbaccess.sce + +### i84114 ### +dbaccess.JoinViewAccessibility +# -> disabled in dbaccess.sce + +### i84116 ### +dbaccess.OQueryDesign +# -> disabled in dbaccess.sce + +### i84125 ### +dbaccess.SbaXGridControl::com::sun::star::view::XSelectionSupplier + +### i84127 ### +dbaccess.SbaXGridControl::com::sun::star::awt::XWindow + +### i86990 ### +dbaccess.ODatabaseSource::com::sun::star::beans::XPropertySet +dbaccess.OCommandDefinition::com::sun::star::beans::XPropertySet + +### i84128 ### +dbaccess.TableWindowAccessibility +# -> disabled in dbaccess.sce + +### i87247 ### +dbaccess.DBContentLoader::com::sun::star::frame::XFrameLoader + +### i88646 ### +dbaccess.ODatabaseContext::com::sun::star::uno::XNamingService + +### i90358 ### +dbaccess.SbaXGridControl::com::sun::star::awt::XView + +### i90359 ### +dbaccess.ODatabaseSource::com::sun::star::sdb::DataSource + +### i95611 ### +dbaccess.SbaXGridControl +# -> disabled in dbaccess.sce + +### i95691 ### +dbaccess.ORowSet::com::sun::star::sdbc::XRowUpdate +dbaccess.ORowSet::com::sun::star::sdbc::XRow + +## i97860 ### +dbaccess.ORowSet::com::sun::star::sdbcx::XDeleteRows +dbaccess.ORowSet::com::sun::star::sdbc::XResultSetUpdate + +### i98007 ### +dbaccess.ODatasourceBrowser +# -> disabled in dbaccess.sce
\ No newline at end of file diff --git a/dbaccess/qa/unoapi/testdocuments/TestDB/testDB.dbf b/dbaccess/qa/unoapi/testdocuments/TestDB/testDB.dbf Binary files differnew file mode 100644 index 000000000..c3af1e143 --- /dev/null +++ b/dbaccess/qa/unoapi/testdocuments/TestDB/testDB.dbf diff --git a/dbaccess/qa/unoapi/testdocuments/TestDB/testDB.dbt b/dbaccess/qa/unoapi/testdocuments/TestDB/testDB.dbt Binary files differnew file mode 100644 index 000000000..41cb9d24c --- /dev/null +++ b/dbaccess/qa/unoapi/testdocuments/TestDB/testDB.dbt |