dbaccess/CppunitTest_dbaccess_SingleSelectQueryComposer_test.mk | 51 + dbaccess/JunitTest_dbaccess_complex.mk | 1 dbaccess/Module_dbaccess.mk | 1 dbaccess/qa/complex/dbaccess/SingleSelectQueryComposer.java | 351 -------- dbaccess/qa/unit/SingleSelectQueryComposer_test.cxx | 436 ++++++++++ 5 files changed, 488 insertions(+), 352 deletions(-)
New commits: commit d1ec7e90772421331bef31eb934180c7f992e42f Author: Adam Seskunas <adamsesku...@gmail.com> AuthorDate: Mon Mar 10 14:10:42 2025 -0700 Commit: Xisco Fauli <xiscofa...@libreoffice.org> CommitDate: Thu Mar 13 21:21:59 2025 +0100 Move SingleSelectQueryComposer.java tests to C++ Move the tests to SingleSelectQueryComposer_test.cxx Remove the old SingleSelectQueryComposer JUnit tests/file Add makefile for SingleSelectQueryComposer_test Remove the java test from JunitTest_dbaccess_complex.mk Add the CppUnit test to the Module_dbaccess.mk file to be run only when Java is enabled on the test machine Change-Id: I832c80e294be6cd4dced4d358bcee383ea04b0fc Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182759 Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> Tested-by: Jenkins diff --git a/dbaccess/CppunitTest_dbaccess_SingleSelectQueryComposer_test.mk b/dbaccess/CppunitTest_dbaccess_SingleSelectQueryComposer_test.mk new file mode 100644 index 000000000000..7be6e2dbbd37 --- /dev/null +++ b/dbaccess/CppunitTest_dbaccess_SingleSelectQueryComposer_test.mk @@ -0,0 +1,51 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# 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/. +# + +$(eval $(call gb_CppunitTest_CppunitTest,dbaccess_SingleSelectQueryComposer_test)) + +$(eval $(call gb_CppunitTest_use_external,dbaccess_SingleSelectQueryComposer_test,boost_headers)) + +$(eval $(call gb_CppunitTest_add_exception_objects,dbaccess_SingleSelectQueryComposer_test, \ + dbaccess/qa/unit/SingleSelectQueryComposer_test \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,dbaccess_SingleSelectQueryComposer_test, \ + comphelper \ + cppu \ + cppuhelper \ + dbaxml \ + dbtools \ + sal \ + subsequenttest \ + utl \ + test \ + tk \ + tl \ + unotest \ + xo \ +)) + +$(eval $(call gb_CppunitTest_use_api,dbaccess_SingleSelectQueryComposer_test,\ + offapi \ + oovbaapi \ + udkapi \ +)) + +$(eval $(call gb_CppunitTest_use_ure,dbaccess_SingleSelectQueryComposer_test)) +$(eval $(call gb_CppunitTest_use_vcl,dbaccess_SingleSelectQueryComposer_test)) + +$(eval $(call gb_CppunitTest_use_rdb,dbaccess_SingleSelectQueryComposer_test,services)) + +$(eval $(call gb_CppunitTest_use_configuration,dbaccess_SingleSelectQueryComposer_test)) + +$(eval $(call gb_CppunitTest_use_uiconfigs,dbaccess_SingleSelectQueryComposer_test, \ + dbaccess \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/dbaccess/JunitTest_dbaccess_complex.mk b/dbaccess/JunitTest_dbaccess_complex.mk index 04609c4413f0..bed3b5231355 100644 --- a/dbaccess/JunitTest_dbaccess_complex.mk +++ b/dbaccess/JunitTest_dbaccess_complex.mk @@ -41,7 +41,6 @@ $(eval $(call gb_JunitTest_add_sourcefiles,dbaccess_complex,\ dbaccess/qa/complex/dbaccess/QueryInQuery \ dbaccess/qa/complex/dbaccess/RowSet \ dbaccess/qa/complex/dbaccess/RowSetEventListener \ - dbaccess/qa/complex/dbaccess/SingleSelectQueryComposer \ dbaccess/qa/complex/dbaccess/TestCase \ dbaccess/qa/complex/dbaccess/UISettings \ )) diff --git a/dbaccess/Module_dbaccess.mk b/dbaccess/Module_dbaccess.mk index f368fcc33e19..960989e5443c 100644 --- a/dbaccess/Module_dbaccess.mk +++ b/dbaccess/Module_dbaccess.mk @@ -59,6 +59,7 @@ $(eval $(call gb_Module_add_check_targets,dbaccess,\ CppunitTest_dbaccess_hsqldb_test \ CppunitTest_dbaccess_RowSetClones \ CppunitTest_dbaccess_CRMDatabase_test \ + CppunitTest_dbaccess_SingleSelectQueryComposer_test \ )) endif diff --git a/dbaccess/qa/complex/dbaccess/SingleSelectQueryComposer.java b/dbaccess/qa/complex/dbaccess/SingleSelectQueryComposer.java deleted file mode 100644 index 656b44d00c04..000000000000 --- a/dbaccess/qa/complex/dbaccess/SingleSelectQueryComposer.java +++ /dev/null @@ -1,351 +0,0 @@ -/* - * 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: 1. " + sExecutableQuery + " 2. " + "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/unit/SingleSelectQueryComposer_test.cxx b/dbaccess/qa/unit/SingleSelectQueryComposer_test.cxx new file mode 100644 index 000000000000..ac9465f22a8f --- /dev/null +++ b/dbaccess/qa/unit/SingleSelectQueryComposer_test.cxx @@ -0,0 +1,436 @@ +/* -*- 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/beans/NamedValue.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/XPropertyContainer.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> + +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/sdb/SQLFilterOperator.hpp> +#include <com/sun/star/sdb/XOfficeDatabaseDocument.hpp> +#include <com/sun/star/sdb/XQueriesSupplier.hpp> +#include <com/sun/star/sdb/XQueryDefinition.hpp> +#include <com/sun/star/sdb/XQueryDefinitionsSupplier.hpp> +#include <com/sun/star/sdb/XSingleSelectQueryComposer.hpp> +#include <com/sun/star/sdb/XParametersSupplier.hpp> +#include <com/sun/star/sdbc/DataType.hpp> +#include <com/sun/star/sdbc/XDataSource.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +class SingleSelectQueryComposerTest : public DBTestBase +{ + Reference<XDataSource> m_xDataSource; + Reference<XConnection> m_xConnection; + Reference<XSingleSelectQueryComposer> m_xComposer; + +public: + virtual void setUp() override; + void testSetCommand(); + void testAttributes(); + void testSubQueries(); + void testParameters(); + void testConditionByColumn(); + void testDisjunctiveNormalForm(); + + CPPUNIT_TEST_SUITE(SingleSelectQueryComposerTest); + CPPUNIT_TEST(testSetCommand); + CPPUNIT_TEST(testAttributes); + CPPUNIT_TEST(testSubQueries); + CPPUNIT_TEST(testParameters); + CPPUNIT_TEST(testConditionByColumn); + CPPUNIT_TEST(testDisjunctiveNormalForm); + CPPUNIT_TEST_SUITE_END(); +}; + +void SingleSelectQueryComposerTest::setUp() +{ + DBTestBase::setUp(); + createDBDocument(u"sdbc:embedded:hsqldb"_ustr); + Reference<sdb::XOfficeDatabaseDocument> xDocument(mxComponent, UNO_QUERY_THROW); + m_xDataSource = xDocument->getDataSource(); + + // Create some common queries + DBTestBase::createQueries(m_xDataSource); + + m_xConnection = m_xDataSource->getConnection(u""_ustr, u""_ustr); + DBTestBase::createTables(m_xConnection); + + // For some reason we must close, then reopen the connection in + // order for the tables to be visible to the connection + m_xConnection->close(); + m_xConnection = m_xDataSource->getConnection(u""_ustr, u""_ustr); + + Reference<lang::XMultiServiceFactory> xFactory(m_xConnection, UNO_QUERY); + Reference<XSingleSelectQueryComposer> xComposer( + xFactory->createInstance(u"com.sun.star.sdb.SingleSelectQueryComposer"_ustr), + UNO_QUERY_THROW); + m_xComposer = xComposer; +} + +// tests setCommand of the composer + +void SingleSelectQueryComposerTest::testSetCommand() +{ + // CommandType::TABLE + m_xComposer->setCommand(u"CUSTOMERS"_ustr, CommandType::TABLE); + CPPUNIT_ASSERT_EQUAL(u"SELECT * FROM \"CUSTOMERS\""_ustr, m_xComposer->getQuery()); + + // CommandType::QUERY + DBTestBase::createQuery(u"SELECT * FROM \"orders for customer\" \"a\", \"customers\" " + "\"b\" WHERE \"a\".\"Product Name\" = \"b\".\"Name\""_ustr, + true, u"set command test"_ustr, m_xDataSource); + + Reference<XQueriesSupplier> xQuerySupplier(m_xConnection, UNO_QUERY_THROW); + Reference<container::XNameAccess> xQueryAccess = xQuerySupplier->getQueries(); + CPPUNIT_ASSERT(xQueryAccess->hasElements()); + Reference<XPropertySet> xQuery(xQueryAccess->getByName(u"set command test"_ustr), UNO_QUERY); + CPPUNIT_ASSERT(xQuery.is()); + + m_xComposer->setCommand(u"set command test"_ustr, CommandType::QUERY); + OUString sQuery; + xQuery->getPropertyValue(u"Command"_ustr) >>= sQuery; + + CPPUNIT_ASSERT_EQUAL(sQuery, m_xComposer->getQuery()); + + // CommandType::COMMMAND + const OUString sCommand + = u"SELECT * FROM \"orders for customer\" WHERE \"Product Name\" = 'test'"_ustr; + m_xComposer->setCommand(sCommand, CommandType::COMMAND); + CPPUNIT_ASSERT_EQUAL(sCommand, m_xComposer->getQuery()); +} + +// tests accessing attributes of the composer (order, filter, group by, having) + +void SingleSelectQueryComposerTest::testAttributes() +{ + const OUString sSimpleQuery2 = u"SELECT * FROM \"CUSTOMERS\" WHERE \"Name\" = 'oranges'"_ustr; + m_xComposer->setElementaryQuery(sSimpleQuery2); + CPPUNIT_ASSERT_EQUAL(sSimpleQuery2, m_xComposer->getQuery()); + + const OUString sSimpleQuery = u"SELECT * FROM \"CUSTOMERS\""_ustr; + m_xComposer->setQuery(sSimpleQuery); + CPPUNIT_ASSERT_EQUAL(sSimpleQuery, m_xComposer->getQuery()); + + // checkAttributeAccess "Filter" + OUString sFilter = u"\"NAME\" = 'oranges'"_ustr; + m_xComposer->setFilter(sFilter); + CPPUNIT_ASSERT_EQUAL(sFilter, m_xComposer->getFilter()); + + // checkAttributeAccess "Group" + sFilter = u"\"CITY\""_ustr; + m_xComposer->setGroup(sFilter); + CPPUNIT_ASSERT_EQUAL(sFilter, m_xComposer->getGroup()); + + // checkAttributeAccess "Order" + sFilter = u"\"ADDRESS\""_ustr; + m_xComposer->setOrder(sFilter); + CPPUNIT_ASSERT_EQUAL(sFilter, m_xComposer->getOrder()); + + // checkAttributeAccess "HavingClause" + sFilter = u"\"ID\" <> 4"_ustr; + m_xComposer->setHavingClause(sFilter); + CPPUNIT_ASSERT_EQUAL(sFilter, m_xComposer->getHavingClause()); + + // check getOrderColumns + Reference<container::XIndexAccess> orderColumns(m_xComposer->getOrderColumns(), UNO_SET_THROW); + CPPUNIT_ASSERT(orderColumns->hasElements()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), orderColumns->getCount()); + + const Reference<XPropertySet> xOrderColumns(orderColumns->getByIndex(0), UNO_QUERY_THROW); + OUString sColumnName; + xOrderColumns->getPropertyValue(u"Name"_ustr) >>= sColumnName; + CPPUNIT_ASSERT_EQUAL(u"ADDRESS"_ustr, sColumnName); + + // check getGroupColumns + Reference<container::XIndexAccess> groupColumns(m_xComposer->getGroupColumns(), UNO_SET_THROW); + CPPUNIT_ASSERT(groupColumns->hasElements()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), groupColumns->getCount()); + + const Reference<XPropertySet> xGroupColumns(groupColumns->getByIndex(0), UNO_QUERY_THROW); + OUString sGroupColumnName; + xGroupColumns->getPropertyValue(u"Name"_ustr) >>= sGroupColumnName; + CPPUNIT_ASSERT_EQUAL(u"CITY"_ustr, sGroupColumnName); + + // XColumnSupplier + Reference<sdbcx::XColumnsSupplier> xSelectColumns(m_xComposer, UNO_QUERY_THROW); + Reference<container::XNameAccess> xColumnAccess = xSelectColumns->getColumns(); + CPPUNIT_ASSERT(xColumnAccess->hasElements()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(6), xColumnAccess->getElementNames().getLength()); + + const OUString COMPLEXFILTER = u"( \"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' } )"_ustr; + + // structured filter + m_xComposer->setQuery(u"SELECT \"ID\", \"POSTAL\", \"ADDRESS\" FROM \"CUSTOMERS\""_ustr); + m_xComposer->setFilter(COMPLEXFILTER); + Sequence<Sequence<PropertyValue>> aStructuredFilter = m_xComposer->getStructuredFilter(); + m_xComposer->setFilter(u""_ustr); + m_xComposer->setStructuredFilter(aStructuredFilter); + CPPUNIT_ASSERT_EQUAL(COMPLEXFILTER, m_xComposer->getFilter()); + + // structured having clause + m_xComposer->setHavingClause(COMPLEXFILTER); + Sequence<Sequence<PropertyValue>> aStructuredHaving = m_xComposer->getStructuredHavingClause(); + m_xComposer->setHavingClause(""); + m_xComposer->setStructuredHavingClause(aStructuredHaving); + CPPUNIT_ASSERT_EQUAL(COMPLEXFILTER, m_xComposer->getHavingClause()); +} + +// test various sub query related features ("queries in queries") + +void SingleSelectQueryComposerTest::testSubQueries() +{ + const OUString INNERPRODUCTSQUERY = u"products (inner)"_ustr; + DBTestBase::createQuery(u"SELECT * FROM \"PRODUCTS\""_ustr, true, INNERPRODUCTSQUERY, + m_xDataSource); + + m_xComposer->setQuery(u"SELECT * FROM \""_ustr + INNERPRODUCTSQUERY + u"\""_ustr); + + Reference<sdbcx::XTablesSupplier> suppTables(m_xComposer, UNO_QUERY_THROW); + Reference<container::XNameAccess> tables = suppTables->getTables(); + CPPUNIT_ASSERT(tables->hasElements()); + CPPUNIT_ASSERT(tables->hasByName(INNERPRODUCTSQUERY)); + + Reference<XQueriesSupplier> xQuerySupplier(m_xConnection, UNO_QUERY_THROW); + Reference<container::XNameAccess> xQueryAccess = xQuerySupplier->getQueries(); + CPPUNIT_ASSERT(xQueryAccess->hasElements()); + + Reference<XPropertySet> xQuery(xQueryAccess->getByName(INNERPRODUCTSQUERY), UNO_QUERY); + OUString sInnerProductsQuery; + xQuery->getPropertyValue(u"Command"_ustr) >>= sInnerProductsQuery; + + const OUString sExpectedQuery = u"SELECT * FROM ( "_ustr + sInnerProductsQuery + + u" )" + " AS \""_ustr + + INNERPRODUCTSQUERY + u"\""_ustr; + const OUString sExecutableQuery = m_xComposer->getQueryWithSubstitution(); + + CPPUNIT_ASSERT_EQUAL(sExpectedQuery, sExecutableQuery); +} + +// test the XParametersSupplier functionality + +void SingleSelectQueryComposerTest::testParameters() +{ + // "orders for customers" is a query with a named parameter (based on another query) + DBTestBase::createQuery( + u"SELECT * FROM \"orders for customer\" WHERE \"Product Name\" LIKE ?"_ustr, true, + u"orders for customer and product"_ustr, m_xDataSource); + + // "orders for customer and product" is query based on "orders for customers", adding an additional, + DBTestBase::createQuery( + u"SELECT * FROM \"all orders\" WHERE \"Customer Name\" LIKE :cname"_ustr, true, + u"orders for customer"_ustr, m_xDataSource); + + Reference<XQueryDefinitionsSupplier> xQuerySupplier(m_xDataSource, UNO_QUERY); + Reference<container::XNameAccess> xQueryAccess = xQuerySupplier->getQueryDefinitions(); + Reference<sdb::XQueryDefinition> xQueryDefinition( + xQueryAccess->getByName(u"orders for customer and product"_ustr), UNO_QUERY); + + OUString sCustomersAndProduct; + xQueryDefinition->getPropertyValue(u"Command"_ustr) >>= sCustomersAndProduct; + + m_xComposer->setQuery(sCustomersAndProduct); + + Reference<XParametersSupplier> xSuppParams(m_xComposer, UNO_QUERY_THROW); + Reference<container::XIndexAccess> xParameters = xSuppParams->getParameters(); + + Sequence<OUString> const expectedParameters{ u"cname"_ustr, u"Product Name"_ustr }; + CPPUNIT_ASSERT_EQUAL(expectedParameters.getLength(), xParameters->getCount()); + + for (auto i = 0; i < expectedParameters.getLength(); ++i) + { + Reference<XPropertySet> xParam(xParameters->getByIndex(i), UNO_QUERY); + OUString sParamName; + xParam->getPropertyValue(u"Name"_ustr) >>= sParamName; + CPPUNIT_ASSERT_EQUAL(expectedParameters[i], sParamName); + } +} + +void SingleSelectQueryComposerTest::testConditionByColumn() +{ + m_xComposer->setQuery("SELECT * FROM \"CUSTOMERS\""); + Sequence<Any> aArgs{ Any(NamedValue(u"AutomaticAddition"_ustr, Any(true))) }; + Reference<beans::XPropertyContainer> filter( + m_xSFactory->createInstanceWithArguments(u"com.sun.star.beans.PropertyBag"_ustr, aArgs), + UNO_QUERY); + + filter->addProperty(u"Name"_ustr, PropertyAttribute::MAYBEVOID, Any(u"COMMENT"_ustr)); + filter->addProperty(u"RealName"_ustr, PropertyAttribute::MAYBEVOID, Any(u"COMMENT"_ustr)); + filter->addProperty(u"TableName"_ustr, PropertyAttribute::MAYBEVOID, Any(u"CUSTOMERS"_ustr)); + filter->addProperty(u"Value"_ustr, PropertyAttribute::MAYBEVOID, Any(u"Good one."_ustr)); + filter->addProperty(u"Type"_ustr, PropertyAttribute::MAYBEVOID, + Any(sal_Int32(sdbc::DataType::LONGVARCHAR))); + + Reference<XPropertySet> xColumn(filter, UNO_QUERY_THROW); + m_xComposer->appendFilterByColumn(xColumn, true, SQLFilterOperator::LIKE); + + Reference<XStatement> xStatement = m_xConnection->createStatement(); + Reference<XResultSet> xResults = xStatement->executeQuery(m_xComposer->getQuery()); + CPPUNIT_ASSERT(xResults.is()); + + // At least one row should exist + Reference<XRow> xRow(xResults, UNO_QUERY_THROW); + CPPUNIT_ASSERT(xResults->next()); + // The row returned should be the 4th, i.e. the one that + // contains the Value "Good One" + CPPUNIT_ASSERT_EQUAL(u"4"_ustr, xRow->getString(1)); +} + +// tests the disjunctive normal form functionality, aka the structured filter, +// of the composer + +void SingleSelectQueryComposerTest::testDisjunctiveNormalForm() +{ + // a simple case: WHERE clause simply is a combination of predicates knitted with AND + OUString sQuery = u"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\" " + ") "_ustr; + + m_xComposer->setQuery(sQuery); + + { + Sequence<Sequence<PropertyValue>> disjunctiveNormalForm + = m_xComposer->getStructuredFilter(); + + Sequence<PropertyValue> expectedDNF(3); + PropertyValue* pExpectedDNF = expectedDNF.getArray(); + + pExpectedDNF[0].Name = u"CUSTOMERID"_ustr; + pExpectedDNF[0].Handle = SQLFilterOperator::EQUAL; + pExpectedDNF[0].Value <<= u"\"CUSTOMERS\".\"ID\""_ustr; + pExpectedDNF[0].State = PropertyState_DIRECT_VALUE; + + pExpectedDNF[1].Name = u"ORDERID"_ustr; + pExpectedDNF[1].Handle = SQLFilterOperator::EQUAL; + pExpectedDNF[1].Value <<= u"\"ORDER\".\"ID\""_ustr; + pExpectedDNF[1].State = PropertyState_DIRECT_VALUE; + + pExpectedDNF[2].Name = u"PRODUCTID"_ustr; + pExpectedDNF[2].Handle = SQLFilterOperator::EQUAL; + pExpectedDNF[2].Value <<= u"\"PRODUCT\".\"ID\""_ustr; + pExpectedDNF[2].State = PropertyState_DIRECT_VALUE; + + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), disjunctiveNormalForm.getLength()); + + for (auto i = 0; i < expectedDNF.getLength(); ++i) + { + CPPUNIT_ASSERT_EQUAL(expectedDNF[i].Name, disjunctiveNormalForm[0][i].Name); + } + } + + // somewhat more challenging: One of the conjunction terms is a disjunction itself + sQuery = u"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 " + ")"_ustr; + + m_xComposer->setQuery(sQuery); + Sequence<Sequence<PropertyValue>> disjunctiveNormalForm = m_xComposer->getStructuredFilter(); + + { + Sequence<PropertyValue> firstExpectedDNF(4); + PropertyValue* pFirstExpectedDNF = firstExpectedDNF.getArray(); + + pFirstExpectedDNF[0].Name = u"CUSTOMERID"_ustr; + pFirstExpectedDNF[0].Handle = SQLFilterOperator::EQUAL; + pFirstExpectedDNF[0].Value <<= u"\"CUSTOMERS\".\"ID\""_ustr; + pFirstExpectedDNF[0].State = PropertyState_DIRECT_VALUE; + + pFirstExpectedDNF[1].Name = u"ORDERID"_ustr; + pFirstExpectedDNF[1].Handle = SQLFilterOperator::EQUAL; + pFirstExpectedDNF[1].Value <<= u"\"ORDER\".\"ID\""_ustr; + pFirstExpectedDNF[1].State = PropertyState_DIRECT_VALUE; + + pFirstExpectedDNF[2].Name = u"PRODUCTID"_ustr; + pFirstExpectedDNF[2].Handle = SQLFilterOperator::EQUAL; + pFirstExpectedDNF[2].Value <<= u"\"PRODUCT\".\"ID\""_ustr; + pFirstExpectedDNF[2].State = PropertyState_DIRECT_VALUE; + + pFirstExpectedDNF[3].Name = u"Name"_ustr; + pFirstExpectedDNF[3].Handle = SQLFilterOperator::EQUAL; + pFirstExpectedDNF[3].Value <<= u"\"Apples\""_ustr; + pFirstExpectedDNF[3].State = PropertyState_DIRECT_VALUE; + + Sequence<PropertyValue> secondExpectedDNF(4); + PropertyValue* pSecondExpectedDNF = secondExpectedDNF.getArray(); + + pSecondExpectedDNF[0].Name = u"CUSTOMERID"_ustr; + pSecondExpectedDNF[0].Handle = SQLFilterOperator::EQUAL; + pSecondExpectedDNF[0].Value <<= u"\"CUSTOMERS\".\"ID\""_ustr; + pSecondExpectedDNF[0].State = PropertyState_DIRECT_VALUE; + + pSecondExpectedDNF[1].Name = u"ORDERID"_ustr; + pSecondExpectedDNF[1].Handle = SQLFilterOperator::EQUAL; + pSecondExpectedDNF[1].Value <<= u"\"ORDER\".\"ID\""_ustr; + pSecondExpectedDNF[1].State = PropertyState_DIRECT_VALUE; + + pSecondExpectedDNF[2].Name = u"PRODUCTID"_ustr; + pSecondExpectedDNF[2].Handle = SQLFilterOperator::EQUAL; + pSecondExpectedDNF[2].Value <<= u"\"PRODUCT\".\"ID\""_ustr; + pSecondExpectedDNF[2].State = PropertyState_DIRECT_VALUE; + + pSecondExpectedDNF[3].Name = u"ID"_ustr; + pSecondExpectedDNF[3].Handle = SQLFilterOperator::EQUAL; + pSecondExpectedDNF[3].Value <<= sal_Int32(2); + pSecondExpectedDNF[3].State = PropertyState_DIRECT_VALUE; + + Sequence<Sequence<PropertyValue>> expectedDNF(2); + Sequence<PropertyValue>* pExpectedDNF = expectedDNF.getArray(); + pExpectedDNF[0] = firstExpectedDNF; + pExpectedDNF[1] = secondExpectedDNF; + + CPPUNIT_ASSERT_EQUAL(expectedDNF.getLength(), disjunctiveNormalForm.getLength()); + + for (auto i = 0; i < expectedDNF.getLength(); ++i) + { + for (auto j = 0; j < expectedDNF[0].getLength(); ++j) + { + CPPUNIT_ASSERT_EQUAL(expectedDNF[i][j].Name, disjunctiveNormalForm[i][j].Name); + } + } + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(SingleSelectQueryComposerTest); + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */