tdf#96370 rework filtering to be aware of WHERE vs HAVING clause

Several bugs (AFAIK not filed into tdf bugzilla) fixed.

Remaining problems:

When some filter clauses go into WHERE and others in HAVING,
they are always logically ANDed (it cannot be any other way),
but that is not communicated to the user in the UI.

Some things left undone:

* DatabaseDataProvider (and its users?) needs to be updated
  to be HAVING-aware, too.

* Form-based filter (.uno:FormFilter) not HAVING-aware
  it reads the current filter in function
  svxform::FormController::setFilter
  in
  svx/source/form/formcontrollers.cxx
  That's one place that needs to be updated.
  The other place is the one that applies the filter.

Change-Id: I0e9d30a1927b6739a16ae7627e8d0dae8823b376
diff --git a/connectivity/source/commontools/dbtools2.cxx b/connectivity/source/commontools/dbtools2.cxx
index 3db5b43..43f4aae 100644
--- a/connectivity/source/commontools/dbtools2.cxx
+++ b/connectivity/source/commontools/dbtools2.cxx
@@ -52,6 +52,7 @@ namespace dbtools

    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;
    using namespace ::com::sun::star::lang;
@@ -1007,6 +1008,40 @@ OUString getDefaultReportEngineServiceName(const Reference< XComponentContext >&
    return OUString();
}

bool isAggregateColumn(const Reference< XSingleSelectQueryComposer > &_xParser, const Reference< XPropertySet > &_xField, bool whenNotFound)
{
    OUString sName;
    _xField->getPropertyValue("Name") >>= sName;
    Reference< XColumnsSupplier > xColumnsSupplier(_xParser, UNO_QUERY);
    Reference< css::container::XNameAccess >  xCols;
    if (xColumnsSupplier.is())
        xCols = xColumnsSupplier->getColumns();

    return isAggregateColumn(xCols, sName, whenNotFound);
}

bool isAggregateColumn(const Reference< XNameAccess > &_xColumns, const OUString &_sName, bool whenNotFound)
{
    if ( _xColumns.is() && _xColumns->hasByName(_sName) )
    {
        Reference<XPropertySet> xProp(_xColumns->getByName(_sName),UNO_QUERY);
        assert(xProp.is());
        return isAggregateColumn( xProp );
    }
    return  whenNotFound;
}

bool isAggregateColumn( const Reference< XPropertySet > &_xColumn )
{
    bool bAgg(false);

    static const char sAgg[] = "AggregateFunction";
    if ( _xColumn->getPropertySetInfo()->hasPropertyByName(sAgg) )
        _xColumn->getPropertyValue(sAgg) >>= bAgg;

    return bAgg;
}


}   // namespace dbtools

diff --git a/connectivity/source/commontools/filtermanager.cxx b/connectivity/source/commontools/filtermanager.cxx
index ba2d2df..5894872 100644
--- a/connectivity/source/commontools/filtermanager.cxx
+++ b/connectivity/source/commontools/filtermanager.cxx
@@ -62,20 +62,64 @@ namespace dbtools

    const OUString& FilterManager::getFilterComponent( FilterComponent _eWhich ) const
    {
        return _eWhich == FilterComponent::PublicFilter ? m_aPublicFilterComponent : m_aLinkFilterComponent;
        switch (_eWhich)
        {
        case FilterComponent::PublicFilter:
            return m_aPublicFilterComponent;
        case FilterComponent::PublicHaving:
            return m_aPublicHavingComponent;
        case FilterComponent::LinkFilter:
            return m_aLinkFilterComponent;
        case FilterComponent::LinkHaving:
            return m_aLinkHavingComponent;
        }
        assert(false);

        static OUString sErr("#FilterManager::getFilterComponent unknown component#");
        return sErr;
    }


    void FilterManager::setFilterComponent( FilterComponent _eWhich, const OUString& _rComponent )
    {
        if (_eWhich == FilterComponent::PublicFilter)
        switch (_eWhich)
        {
        case FilterComponent::PublicFilter:
            m_aPublicFilterComponent = _rComponent;
        else
            break;
        case FilterComponent::PublicHaving:
            m_aPublicHavingComponent = _rComponent;
            break;
        case FilterComponent::LinkFilter:
            m_aLinkFilterComponent = _rComponent;
            break;
        case FilterComponent::LinkHaving:
            m_aLinkHavingComponent = _rComponent;
            break;
        }
        try
        {
            if ( m_xComponentAggregate.is() && (( _eWhich != FilterComponent::PublicFilter ) || m_bApplyPublicFilter ) )
                m_xComponentAggregate->setPropertyValue( OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_FILTER), makeAny( getComposedFilter() ) );
            if ( m_xComponentAggregate.is() )
            {
                bool propagate(true);
                switch (_eWhich)
                {
                case FilterComponent::PublicFilter:
                    propagate = propagate && m_bApplyPublicFilter;
                    SAL_FALLTHROUGH;
                case FilterComponent::LinkFilter:
                    if (propagate)
                        m_xComponentAggregate->setPropertyValue( OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_FILTER), makeAny( getComposedFilter() ) );
                    break;
                case FilterComponent::PublicHaving:
                    propagate = propagate && m_bApplyPublicFilter;
                    SAL_FALLTHROUGH;
                case FilterComponent::LinkHaving:
                    if (propagate)
                        m_xComponentAggregate->setPropertyValue( OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_HAVINGCLAUSE), makeAny( getComposedHaving() ) );
                    break;
                }
            }
        }
        catch( const Exception& )
        {
@@ -93,9 +137,13 @@ namespace dbtools

        try
        {
            if ( m_xComponentAggregate.is() && !getFilterComponent( FilterComponent::PublicFilter ).isEmpty() )
            {   // only if there changed something
                m_xComponentAggregate->setPropertyValue( OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_FILTER), makeAny( getComposedFilter() ) );
            if ( m_xComponentAggregate.is())
            {
                // only where/if something changed
                if (!getFilterComponent( FilterComponent::PublicFilter ).isEmpty())
                    m_xComponentAggregate->setPropertyValue( OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_FILTER), makeAny( getComposedFilter() ) );
                if (!getFilterComponent( FilterComponent::PublicHaving ).isEmpty())
                    m_xComponentAggregate->setPropertyValue( OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_HAVINGCLAUSE), makeAny( getComposedHaving() ) );
            }
        }
        catch( const Exception& )
@@ -120,7 +168,7 @@ namespace dbtools
    }


    bool FilterManager::isThereAtMostOneComponent( OUString& o_singleComponent ) const
    bool FilterManager::isThereAtMostOneFilterComponent( OUString& o_singleComponent ) const
    {
        if (m_bApplyPublicFilter) {
            if (!m_aPublicFilterComponent.isEmpty() && !m_aLinkFilterComponent.isEmpty())
@@ -143,12 +191,35 @@ namespace dbtools
        }
    }

    bool FilterManager::isThereAtMostOneHavingComponent( OUString& o_singleComponent ) const
    {
        if (m_bApplyPublicFilter) {
            if (!m_aPublicHavingComponent.isEmpty() && !m_aLinkHavingComponent.isEmpty())
                return false;
            if (!m_aPublicHavingComponent.isEmpty())
                o_singleComponent = m_aPublicHavingComponent;
            else if (!m_aLinkHavingComponent.isEmpty())
                o_singleComponent = m_aLinkHavingComponent;
            else
                o_singleComponent.clear();
            return true;
        }
        else
        {
            if (m_aLinkHavingComponent.isEmpty())
                o_singleComponent.clear();
            else
                o_singleComponent = m_aLinkHavingComponent;
            return true;
        }
    }


    OUString FilterManager::getComposedFilter( ) const
    {
        // if we have only one non-empty component, then there's no need to compose anything
        OUString singleComponent;
        if ( isThereAtMostOneComponent( singleComponent ) )
        if ( isThereAtMostOneFilterComponent( singleComponent ) )
        {
            return singleComponent;
        }
@@ -161,6 +232,23 @@ namespace dbtools
    }


    OUString FilterManager::getComposedHaving( ) const
    {
        // if we have only one non-empty component, then there's no need to compose anything
        OUString singleComponent;
        if ( isThereAtMostOneHavingComponent( singleComponent ) )
        {
            return singleComponent;
        }
        // append the single components
        OUStringBuffer aComposedFilter(singleComponent);
        if (m_bApplyPublicFilter)
            appendFilterComponent( aComposedFilter, m_aPublicHavingComponent );
        appendFilterComponent( aComposedFilter, m_aLinkHavingComponent );
        return aComposedFilter.makeStringAndClear();
    }


}   // namespace dbtools


diff --git a/connectivity/source/commontools/parameters.cxx b/connectivity/source/commontools/parameters.cxx
index 37f6a6b..30311ba 100644
--- a/connectivity/source/commontools/parameters.cxx
+++ b/connectivity/source/commontools/parameters.cxx
@@ -245,7 +245,9 @@ namespace dbtools


    void ParameterManager::classifyLinks( const Reference< XNameAccess >& _rxParentColumns,
        const Reference< XNameAccess >& _rxColumns, std::vector< OUString >& _out_rAdditionalFilterComponents )
        const Reference< XNameAccess >& _rxColumns,
        std::vector< OUString >& _out_rAdditionalFilterComponents,
        std::vector< OUString >& _out_rAdditionalHavingComponents )
    {
        OSL_PRECOND( m_aMasterFields.size() == m_aDetailFields.size(),
            "ParameterManager::classifyLinks: master and detail fields should have the same length!" );
@@ -309,7 +311,10 @@ namespace dbtools
                    aInsertionPos.first->second.eType = ParameterClassification::LinkedByColumnName;

                    // remember the filter component
                    _out_rAdditionalFilterComponents.push_back( sFilterCondition );
                    if (isAggregateColumn(xDetailField))
                        _out_rAdditionalHavingComponents.push_back( sFilterCondition );
                    else
                        _out_rAdditionalFilterComponents.push_back( sFilterCondition );

                    // remember the new "detail field" for this link
                    aStrippedDetailFields.push_back( sNewParamName );
@@ -381,7 +386,8 @@ namespace dbtools

            // classify the links - depending on what the detail fields in each link pair denotes
            std::vector< OUString > aAdditionalFilterComponents;
            classifyLinks( xParentColumns, xColumns, aAdditionalFilterComponents );
            std::vector< OUString > aAdditionalHavingComponents;
            classifyLinks( xParentColumns, xColumns, aAdditionalFilterComponents, aAdditionalHavingComponents );

            // did we find links where the detail field refers to a detail column (instead of a parameter name)?
            if ( !aAdditionalFilterComponents.empty() )
@@ -401,11 +407,34 @@ namespace dbtools
                    sAdditionalFilter.append(" )");
                }

                // now set this filter at the 's filter manager
                // now set this filter at the filter manager
                _rFilterManager.setFilterComponent( FilterManager::FilterComponent::LinkFilter, sAdditionalFilter.makeStringAndClear() );

                _rColumnsInLinkDetails = true;
            }

            if ( !aAdditionalHavingComponents.empty() )
            {
                // build a conjunction of all the filter components
                OUStringBuffer sAdditionalHaving;
                for (   std::vector< OUString >::const_iterator aComponent = aAdditionalHavingComponents.begin();
                        aComponent != aAdditionalHavingComponents.end();
                        ++aComponent
                    )
                {
                    if ( !sAdditionalHaving.isEmpty() )
                        sAdditionalHaving.append(" AND ");

                    sAdditionalHaving.append("( ");
                    sAdditionalHaving.append(*aComponent);
                    sAdditionalHaving.append(" )");
                }

                // now set this having clause at the filter manager
                _rFilterManager.setFilterComponent( FilterManager::FilterComponent::LinkHaving, sAdditionalHaving.makeStringAndClear() );

                _rColumnsInLinkDetails = true;
            }
        }
        catch( const Exception& )
        {
diff --git a/dbaccess/source/core/misc/DatabaseDataProvider.cxx b/dbaccess/source/core/misc/DatabaseDataProvider.cxx
index 0d8b980..d3a823b 100644
--- a/dbaccess/source/core/misc/DatabaseDataProvider.cxx
+++ b/dbaccess/source/core/misc/DatabaseDataProvider.cxx
@@ -47,6 +47,8 @@
#include <vector>
#include <list>

// TODO: update for new HavingClause-aware FilterManager

namespace dbaccess
{
using namespace ::com::sun::star;
diff --git a/dbaccess/source/ui/browser/brwctrlr.cxx b/dbaccess/source/ui/browser/brwctrlr.cxx
index af094e9..4cc6913 100644
--- a/dbaccess/source/ui/browser/brwctrlr.cxx
+++ b/dbaccess/source/ui/browser/brwctrlr.cxx
@@ -2006,18 +2006,7 @@ void SbaXDataBrowserController::Execute(sal_uInt16 nId, const Sequence< Property
                break;

            // check if the column is a aggregate function
            bool bHaving = false;
            OUString sName;
            xField->getPropertyValue(PROPERTY_NAME) >>= sName;
            Reference< XColumnsSupplier > xColumnsSupplier(m_xParser, UNO_QUERY);
            Reference< css::container::XNameAccess >  xCols = xColumnsSupplier.is() ? xColumnsSupplier->getColumns() : Reference< css::container::XNameAccess > ();
            if ( xCols.is() && xCols->hasByName(sName) )
            {
                Reference<XPropertySet> xProp(xCols->getByName(sName),UNO_QUERY);
                static const char sAgg[] = "AggregateFunction";
                if ( xProp->getPropertySetInfo()->hasPropertyByName(sAgg) )
                    xProp->getPropertyValue(sAgg) >>= bHaving;
            }
            const bool bHaving(isAggregateColumn(m_xParser, xField));

            Reference< XSingleSelectQueryComposer > xParser = createParser_nothrow();
            const OUString sOldFilter = xParser->getFilter();
@@ -2029,7 +2018,8 @@ void SbaXDataBrowserController::Execute(sal_uInt16 nId, const Sequence< Property
            // -> completely overwrite it, else append one
            if (!bApplied)
            {
                DO_SAFE( (bHaving ? xParser->setHavingClause(OUString()) : xParser->setFilter(::OUString())), "SbaXDataBrowserController::Execute : caught an exception while resetting the new filter !" );
                DO_SAFE( xParser->setFilter(      OUString()), "SbaXDataBrowserController::Execute : caught an exception while resetting unapplied filter !" );
                DO_SAFE( xParser->setHavingClause(OUString()), "SbaXDataBrowserController::Execute : caught an exception while resetting unapplied HAVING clause !" );
            }

            bool bParserSuccess = false;
diff --git a/dbaccess/source/ui/dlg/queryfilter.cxx b/dbaccess/source/ui/dlg/queryfilter.cxx
index c9678ac..f7d04f0 100644
--- a/dbaccess/source/ui/dlg/queryfilter.cxx
+++ b/dbaccess/source/ui/dlg/queryfilter.cxx
@@ -169,9 +169,10 @@ DlgFilterCrit::DlgFilterCrit(vcl::Window * pParent,

    // insert the criteria into the dialog
    Sequence<Sequence<PropertyValue > > aValues = m_xQueryComposer->getStructuredFilter();
    fillLines(aValues);
    int i(0);
    fillLines(i, aValues);
    aValues = m_xQueryComposer->getStructuredHavingClause();
    fillLines(aValues);
    fillLines(i, aValues);

    EnableLines();

@@ -467,7 +468,7 @@ IMPL_LINK( DlgFilterCrit, PredicateLoseFocus, Control&, rControl, void )
    }
}

void DlgFilterCrit::SetLine( sal_uInt16 nIdx,const PropertyValue& _rItem,bool _bOr  )
void DlgFilterCrit::SetLine( int nIdx, const PropertyValue& _rItem, bool _bOr )
{
    OUString aStr;
    _rItem.Value >>= aStr;
@@ -785,13 +786,13 @@ void DlgFilterCrit::BuildWherePart()
    }
}

void DlgFilterCrit::fillLines(const Sequence<Sequence<PropertyValue > >& _aValues)
void DlgFilterCrit::fillLines(int &i, const Sequence< Sequence< PropertyValue > >& _aValues)
{
    const Sequence<PropertyValue >* pOrIter = _aValues.getConstArray();
    const Sequence<PropertyValue >* pOrEnd   = pOrIter + _aValues.getLength();
    for(sal_uInt16 i=0;pOrIter != pOrEnd; ++pOrIter)
    bool bOr(i != 0); // WHERE clause and HAVING clause are always ANDed, nor ORed
    for(; pOrIter != pOrEnd; ++pOrIter)
    {
        bool bOr = true;
        const PropertyValue* pAndIter   = pOrIter->getConstArray();
        const PropertyValue* pAndEnd    = pAndIter + pOrIter->getLength();
        for(;pAndIter != pAndEnd; ++pAndIter)
@@ -799,6 +800,7 @@ void DlgFilterCrit::fillLines(const Sequence<Sequence<PropertyValue > >& _aValue
            SetLine( i++,*pAndIter,bOr);
            bOr = false;
        }
        bOr=true;
    }
}

diff --git a/dbaccess/source/ui/inc/queryfilter.hxx b/dbaccess/source/ui/inc/queryfilter.hxx
index f7253f1..5dd72f1 100644
--- a/dbaccess/source/ui/inc/queryfilter.hxx
+++ b/dbaccess/source/ui/inc/queryfilter.hxx
@@ -94,12 +94,12 @@ namespace dbaui
        DECL_LINK( ListSelectHdl, ListBox&, void );
        DECL_LINK( ListSelectCompHdl, ListBox&, void );

        void            SetLine( sal_uInt16 nIdx,const css::beans::PropertyValue& _rItem,bool _bOr );
        void            SetLine( int nIdx, const css::beans::PropertyValue& _rItem, bool _bOr );
        void            EnableLines();
        sal_Int32       GetOSQLPredicateType( const OUString& _rSelectedPredicate ) const;
        static sal_Int32  GetSelectionPos(sal_Int32 eType,const ListBox& rListBox);
        bool            getCondition(const ListBox& _rField,const ListBox& _rComp,const Edit& _rValue,css::beans::PropertyValue& _rFilter) const;
        void            fillLines(const css::uno::Sequence< css::uno::Sequence< css::beans::PropertyValue > >& _aValues);
        void            fillLines(int &i, const css::uno::Sequence< css::uno::Sequence< css::beans::PropertyValue > >& _aValues);

        css::uno::Reference< css::beans::XPropertySet > getMatchingColumn( const Edit& _rValueInput ) const;
        css::uno::Reference< css::beans::XPropertySet > getColumn( const OUString& _rFieldName ) const;
diff --git a/forms/source/component/DatabaseForm.cxx b/forms/source/component/DatabaseForm.cxx
index 925d8a8..1aae5d8 100644
--- a/forms/source/component/DatabaseForm.cxx
+++ b/forms/source/component/DatabaseForm.cxx
@@ -1288,7 +1288,7 @@ void ODatabaseForm::describeFixedAndAggregateProperties(
        Sequence< Property >& _rProps,
        Sequence< Property >& _rAggregateProps ) const
{
    _rProps.realloc( 22 );
    _rProps.realloc( 23 );
    css::beans::Property* pProperties = _rProps.getArray();

    if (m_xAggregateSet.is())
@@ -1312,6 +1312,7 @@ void ODatabaseForm::describeFixedAndAggregateProperties(
    // (e.g. the ones which result from linking master fields to detail fields
    // via column names instead of parameters)
    RemoveProperty( _rAggregateProps, PROPERTY_FILTER );
    RemoveProperty( _rAggregateProps, PROPERTY_HAVINGCLAUSE );
    RemoveProperty( _rAggregateProps, PROPERTY_APPLYFILTER );

    DECL_IFACE_PROP4( ACTIVE_CONNECTION,XConnection,             BOUND, TRANSIENT, MAYBEVOID, CONSTRAINED);
@@ -1322,6 +1323,7 @@ void ODatabaseForm::describeFixedAndAggregateProperties(
    DECL_PROP2      ( DATASOURCE,       OUString,                BOUND, CONSTRAINED             );
    DECL_PROP3      ( CYCLE,            TabulatorCycle,          BOUND, MAYBEVOID, MAYBEDEFAULT );
    DECL_PROP2      ( FILTER,           OUString,                BOUND, MAYBEDEFAULT            );
    DECL_PROP2      ( HAVINGCLAUSE,     OUString,                BOUND, MAYBEDEFAULT            );
    DECL_BOOL_PROP2 ( INSERTONLY,                                BOUND, MAYBEDEFAULT            );
    DECL_PROP1      ( NAVIGATION,       NavigationBarMode,       BOUND                          );
    DECL_BOOL_PROP1 ( ALLOWADDITIONS,                            BOUND                          );
@@ -1464,6 +1466,10 @@ void ODatabaseForm::getFastPropertyValue( Any& rValue, sal_Int32 nHandle ) const
            rValue <<= m_aFilterManager.getFilterComponent( FilterManager::FilterComponent::PublicFilter );
            break;

        case PROPERTY_ID_HAVINGCLAUSE:
            rValue <<= m_aFilterManager.getFilterComponent( FilterManager::FilterComponent::PublicHaving );
            break;

        case PROPERTY_ID_APPLYFILTER:
            rValue <<= m_aFilterManager.isApplyPublicFilter();
            break;
@@ -1546,6 +1552,10 @@ sal_Bool ODatabaseForm::convertFastPropertyValue( Any& rConvertedValue, Any& rOl
            bModified = tryPropertyValue( rConvertedValue, rOldValue, rValue, m_aFilterManager.getFilterComponent( FilterManager::FilterComponent::PublicFilter ) );
            break;

        case PROPERTY_ID_HAVINGCLAUSE:
            bModified = tryPropertyValue( rConvertedValue, rOldValue, rValue, m_aFilterManager.getFilterComponent( FilterManager::FilterComponent::PublicHaving ) );
            break;

        case PROPERTY_ID_APPLYFILTER:
            bModified = tryPropertyValue( rConvertedValue, rOldValue, rValue, m_aFilterManager.isApplyPublicFilter() );
            break;
@@ -1635,6 +1645,14 @@ void ODatabaseForm::setFastPropertyValue_NoBroadcast( sal_Int32 nHandle, const A
        }
        break;

        case PROPERTY_ID_HAVINGCLAUSE:
        {
            OUString sNewFilter;
            rValue >>= sNewFilter;
            m_aFilterManager.setFilterComponent( FilterManager::FilterComponent::PublicHaving, sNewFilter );
        }
        break;

        case PROPERTY_ID_APPLYFILTER:
        {
            bool bApply = true;
@@ -1780,6 +1798,13 @@ PropertyState ODatabaseForm::getPropertyStateByHandle(sal_Int32 nHandle)
                eState = PropertyState_DIRECT_VALUE;
            break;

        case PROPERTY_ID_HAVINGCLAUSE:
            if ( m_aFilterManager.getFilterComponent( FilterManager::FilterComponent::PublicHaving ).isEmpty() )
                eState = PropertyState_DEFAULT_VALUE;
            else
                eState = PropertyState_DIRECT_VALUE;
            break;

        case PROPERTY_ID_APPLYFILTER:
            eState = m_aFilterManager.isApplyPublicFilter() ? PropertyState_DEFAULT_VALUE : PropertyState_DIRECT_VALUE;
            break;
@@ -1813,6 +1838,7 @@ void ODatabaseForm::setPropertyToDefaultByHandle(sal_Int32 nHandle)
    {
        case PROPERTY_ID_INSERTONLY:
        case PROPERTY_ID_FILTER:
        case PROPERTY_ID_HAVINGCLAUSE:
        case PROPERTY_ID_APPLYFILTER:
        case PROPERTY_ID_NAVIGATION:
        case PROPERTY_ID_CYCLE:
@@ -1843,6 +1869,10 @@ Any ODatabaseForm::getPropertyDefaultByHandle( sal_Int32 nHandle ) const
            aReturn <<= OUString();
            break;

        case PROPERTY_ID_HAVINGCLAUSE:
            aReturn <<= OUString();
            break;

        case PROPERTY_ID_APPLYFILTER:
            aReturn <<= true;
            break;
@@ -3741,7 +3771,7 @@ void SAL_CALL ODatabaseForm::write(const Reference<XObjectOutputStream>& _rxOutS
    OFormComponents::write(_rxOutStream);

    // version
    _rxOutStream->writeShort(0x0003);
    _rxOutStream->writeShort(0x0004);

    // Name
    _rxOutStream << m_sName;
@@ -3819,10 +3849,13 @@ void SAL_CALL ODatabaseForm::write(const Reference<XObjectOutputStream>& _rxOutS
    _rxOutStream->writeShort((sal_Int16)m_eNavigation);

    OUString sFilter;
    OUString sHaving;
    OUString sOrder;
    if (m_xAggregateSet.is())
    {
        m_xAggregateSet->getPropertyValue(PROPERTY_FILTER) >>= sFilter;
        // version 4
        m_xAggregateSet->getPropertyValue(PROPERTY_HAVINGCLAUSE) >>= sHaving;
        m_xAggregateSet->getPropertyValue(PROPERTY_SORT) >>= sOrder;
    }
    _rxOutStream << sFilter;
@@ -3922,6 +3955,11 @@ void SAL_CALL ODatabaseForm::read(const Reference<XObjectInputStream>& _rxInStre

        _rxInStream >> sAggregateProp;
        setPropertyValue(PROPERTY_FILTER, makeAny(sAggregateProp));
        if(nVersion > 3)
        {
            _rxInStream >> sAggregateProp;
            setPropertyValue(PROPERTY_HAVINGCLAUSE, makeAny(sAggregateProp));
        }

        _rxInStream >> sAggregateProp;
        if (m_xAggregateSet.is())
diff --git a/forms/source/inc/frm_strings.hxx b/forms/source/inc/frm_strings.hxx
index 868772a..790f279 100644
--- a/forms/source/inc/frm_strings.hxx
+++ b/forms/source/inc/frm_strings.hxx
@@ -53,6 +53,7 @@ namespace frm
    #define PROPERTY_RELEVANT                 "Relevant"
    #define PROPERTY_ISREADONLY               "IsReadOnly"
    #define PROPERTY_FILTER                   "Filter"
    #define PROPERTY_HAVINGCLAUSE             "HavingClause"
    #define PROPERTY_WIDTH                    "Width"
    #define PROPERTY_SEARCHABLE               "IsSearchable"
    #define PROPERTY_MULTILINE                "MultiLine"
diff --git a/forms/source/inc/property.hxx b/forms/source/inc/property.hxx
index b5d879a..ea2ffca 100644
--- a/forms/source/inc/property.hxx
+++ b/forms/source/inc/property.hxx
@@ -173,7 +173,7 @@ namespace frm
#define PROPERTY_ID_AUTOINCREMENT       (PROPERTY_ID_START +133)    // UINT16
    // free
#define PROPERTY_ID_FILTER              (PROPERTY_ID_START +135)    // ::rtl::OUString
    // free
#define PROPERTY_ID_HAVINGCLAUSE        (PROPERTY_ID_START +136)    // ::rtl::OUString
#define PROPERTY_ID_QUERY               (PROPERTY_ID_START +137)    // ::rtl::OUString
#define PROPERTY_ID_DEFAULT_LONG_VALUE  (PROPERTY_ID_START +138)    // Double
#define PROPERTY_ID_DEFAULT_DATE        (PROPERTY_ID_START +139)    // UINT32
diff --git a/forms/source/runtime/formoperations.cxx b/forms/source/runtime/formoperations.cxx
index 340b451..e5f9df5 100644
--- a/forms/source/runtime/formoperations.cxx
+++ b/forms/source/runtime/formoperations.cxx
@@ -307,8 +307,10 @@ namespace frm
            case FormFeature::ToggleApplyFilter:
            {
                OUString sFilter;
                m_xCursorProperties->getPropertyValue( PROPERTY_FILTER ) >>= sFilter;
                if ( !sFilter.isEmpty() )
                OUString sHaving;
                m_xCursorProperties->getPropertyValue( PROPERTY_FILTER )       >>= sFilter;
                m_xCursorProperties->getPropertyValue( PROPERTY_HAVINGCLAUSE ) >>= sHaving;
                if ( ! (sFilter.isEmpty() && sHaving.isEmpty()) )
                {
                    aState.State = m_xCursorProperties->getPropertyValue( PROPERTY_APPLYFILTER );
                    aState.Enabled = !impl_isInsertOnlyForm_throw();
@@ -718,13 +720,15 @@ namespace frm
                OSL_ENSURE( xProperties.is(), "FormOperations::execute: no multi property access!" );
                if ( xProperties.is() )
                {
                    Sequence< OUString > aNames( 2 );
                    Sequence< OUString > aNames( 3 );
                    aNames[0] = PROPERTY_FILTER;
                    aNames[1] = PROPERTY_SORT;
                    aNames[1] = PROPERTY_HAVINGCLAUSE;
                    aNames[2] = PROPERTY_SORT;

                    Sequence< Any> aValues( 2 );
                    Sequence< Any> aValues( 3 );
                    aValues[0] <<= OUString();
                    aValues[1] <<= OUString();
                    aValues[2] <<= OUString();

                    WaitObject aWO( nullptr );
                    xProperties->setPropertyValues( aNames, aValues );
@@ -1034,6 +1038,11 @@ namespace frm
                    if ( m_xParser->getFilter() != sNewValue )
                        m_xParser->setFilter( sNewValue );
                }
                else if ( _rEvent.PropertyName == PROPERTY_HAVINGCLAUSE )
                {
                    if ( m_xParser->getHavingClause() != sNewValue )
                        m_xParser->setHavingClause( sNewValue );
                }
                else if ( _rEvent.PropertyName == PROPERTY_SORT )
                {
                    _rEvent.NewValue >>= sNewValue;
@@ -1221,14 +1230,17 @@ namespace frm
                {
                    OUString sStatement;
                    OUString sFilter;
                    OUString sHaving;
                    OUString sSort;

                    m_xCursorProperties->getPropertyValue( PROPERTY_ACTIVECOMMAND   ) >>= sStatement;
                    m_xCursorProperties->getPropertyValue( PROPERTY_FILTER          ) >>= sFilter;
                    m_xCursorProperties->getPropertyValue( PROPERTY_HAVINGCLAUSE    ) >>= sHaving;
                    m_xCursorProperties->getPropertyValue( PROPERTY_SORT            ) >>= sSort;

                    m_xParser->setElementaryQuery( sStatement );
                    m_xParser->setFilter         ( sFilter    );
                    m_xParser->setHavingClause   ( sHaving    );
                    m_xParser->setOrder          ( sSort      );
                }

@@ -1236,6 +1248,7 @@ namespace frm
                // we can keep our parser in sync
                m_xCursorProperties->addPropertyChangeListener( PROPERTY_ACTIVECOMMAND, this );
                m_xCursorProperties->addPropertyChangeListener( PROPERTY_FILTER, this );
                m_xCursorProperties->addPropertyChangeListener( PROPERTY_HAVINGCLAUSE, this );
                m_xCursorProperties->addPropertyChangeListener( PROPERTY_SORT, this );
            }
        }
@@ -1257,6 +1270,7 @@ namespace frm
            if ( m_xParser.is() && m_xCursorProperties.is() )
            {
                m_xCursorProperties->removePropertyChangeListener( PROPERTY_FILTER, this );
                m_xCursorProperties->removePropertyChangeListener( PROPERTY_HAVINGCLAUSE, this );
                m_xCursorProperties->removePropertyChangeListener( PROPERTY_ACTIVECOMMAND, this );
                m_xCursorProperties->removePropertyChangeListener( PROPERTY_SORT, this );
            }
@@ -1337,7 +1351,9 @@ namespace frm

    bool FormOperations::impl_hasFilterOrOrder_throw() const
    {
        return impl_isParseable_throw() && ( !m_xParser->getFilter().isEmpty() || !m_xParser->getOrder().isEmpty() );
        return impl_isParseable_throw() && ( !m_xParser->getFilter().isEmpty() ||
                                             !m_xParser->getHavingClause().isEmpty() ||
                                             !m_xParser->getOrder().isEmpty() );
    }


@@ -1600,21 +1616,27 @@ namespace frm
                return;

            OUString sOriginalFilter;
            m_xCursorProperties->getPropertyValue( PROPERTY_FILTER ) >>= sOriginalFilter;
            OUString sOriginalHaving;
            m_xCursorProperties->getPropertyValue( PROPERTY_FILTER       ) >>= sOriginalFilter;
            m_xCursorProperties->getPropertyValue( PROPERTY_HAVINGCLAUSE ) >>= sOriginalHaving;
            bool bApplied = true;
            m_xCursorProperties->getPropertyValue( PROPERTY_APPLYFILTER ) >>= bApplied;

            // if we have a filter, but it's not applied, then we have to overwrite it, else append one
            if ( !bApplied )
            {
                m_xParser->setFilter( OUString() );
                m_xParser->setHavingClause( OUString() );
            }

            impl_appendFilterByColumn_throw aAction(this, xBoundField);
            impl_appendFilterByColumn_throw aAction(this, m_xParser, xBoundField);
            impl_doActionInSQLContext_throw( aAction, RID_STR_COULD_NOT_SET_FILTER );

            WaitObject aWO( nullptr );
            try
            {
                m_xCursorProperties->setPropertyValue( PROPERTY_FILTER, makeAny( m_xParser->getFilter() ) );
                m_xCursorProperties->setPropertyValue( PROPERTY_FILTER,       makeAny( m_xParser->getFilter() ) );
                m_xCursorProperties->setPropertyValue( PROPERTY_HAVINGCLAUSE, makeAny( m_xParser->getHavingClause() ) );
                m_xCursorProperties->setPropertyValue( PROPERTY_APPLYFILTER, makeAny( true ) );

                m_xLoadableForm->reload();
@@ -1629,9 +1651,11 @@ namespace frm
            {   // something went wrong -> restore the original state
                try
                {
                    m_xParser->setOrder( sOriginalFilter );
                    m_xParser->setFilter      ( sOriginalFilter );
                    m_xParser->setHavingClause( sOriginalHaving );
                    m_xCursorProperties->setPropertyValue( PROPERTY_APPLYFILTER, makeAny( bApplied ) );
                    m_xCursorProperties->setPropertyValue( PROPERTY_FILTER, makeAny( m_xParser->getFilter() ) );
                    m_xCursorProperties->setPropertyValue( PROPERTY_FILTER,       makeAny( m_xParser->getFilter() ) );
                    m_xCursorProperties->setPropertyValue( PROPERTY_HAVINGCLAUSE, makeAny( m_xParser->getHavingClause() ) );
                    m_xLoadableForm->reload();
                }
                catch( const Exception& )
@@ -1678,7 +1702,10 @@ namespace frm
            {
                WaitObject aWO( nullptr );
                if ( _bFilter )
                    m_xCursorProperties->setPropertyValue( PROPERTY_FILTER, makeAny( m_xParser->getFilter() ) );
                {
                    m_xCursorProperties->setPropertyValue( PROPERTY_FILTER,       makeAny( m_xParser->getFilter() ) );
                    m_xCursorProperties->setPropertyValue( PROPERTY_HAVINGCLAUSE, makeAny( m_xParser->getHavingClause() ) );
                }
                else
                    m_xCursorProperties->setPropertyValue( PROPERTY_SORT, makeAny( m_xParser->getOrder() ) );
                m_xLoadableForm->reload();
diff --git a/forms/source/runtime/formoperations.hxx b/forms/source/runtime/formoperations.hxx
index 425e789f..ad1494f 100644
--- a/forms/source/runtime/formoperations.hxx
+++ b/forms/source/runtime/formoperations.hxx
@@ -25,8 +25,10 @@
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/form/XForm.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/form/XLoadable.hpp>
#include <com/sun/star/sdb/XSingleSelectQueryComposer.hpp>
#include <com/sun/star/sdbcx/XColumnsSupplier.hpp>
#include <com/sun/star/util/XModifyListener.hpp>
#include <com/sun/star/container/XIndexAccess.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
@@ -35,7 +37,7 @@

#include <cppuhelper/basemutex.hxx>
#include <cppuhelper/compbase.hxx>

#include <connectivity/dbtools.hxx>

namespace frm
{
@@ -320,14 +322,22 @@ namespace frm
        {
        public:
            impl_appendFilterByColumn_throw(const FormOperations *pFO,
                                            css::uno::Reference< css::sdb::XSingleSelectQueryComposer > const & xParser,
                                            css::uno::Reference< css::beans::XPropertySet > const & xField)
                : m_pFO(pFO)
                , m_xParser(xParser)
                , m_xField(xField)
            {};

            void operator()() { m_pFO->m_xParser->appendFilterByColumn( m_xField, true, css::sdb::SQLFilterOperator::EQUAL ); }
            void operator()() {
                if (dbtools::isAggregateColumn( m_xParser, m_xField ))
                    m_pFO->m_xParser->appendHavingClauseByColumn( m_xField, true, css::sdb::SQLFilterOperator::EQUAL );
                else
                    m_pFO->m_xParser->appendFilterByColumn( m_xField, true, css::sdb::SQLFilterOperator::EQUAL );
            }
        private:
            const FormOperations *m_pFO;
            css::uno::Reference< css::sdb::XSingleSelectQueryComposer > m_xParser;
            css::uno::Reference< css::beans::XPropertySet > m_xField;
        };

diff --git a/include/connectivity/dbtools.hxx b/include/connectivity/dbtools.hxx
index f771ae9..e0bf5ca8 100644
--- a/include/connectivity/dbtools.hxx
+++ b/include/connectivity/dbtools.hxx
@@ -20,6 +20,7 @@
#ifndef INCLUDED_CONNECTIVITY_DBTOOLS_HXX
#define INCLUDED_CONNECTIVITY_DBTOOLS_HXX

#include <com/sun/star/sdb/XSingleSelectQueryComposer.hpp>
#include <connectivity/dbexception.hxx>
#include <comphelper/types.hxx>
#include <com/sun/star/sdbc/DataType.hpp>
@@ -787,6 +788,44 @@ namespace dbtools
            OUStringBuffer& _out_rSQLPredicate
        );

    /** is this field an aggregate?

        @param _xComposer
            a query composer that knows the field by name
        @param _xField
            the field
        @param whenNotFound
            value returned when _sName is not known by _xComposer
    */
    OOO_DLLPUBLIC_DBTOOLS bool isAggregateColumn(
            const css::uno::Reference< css::sdb::XSingleSelectQueryComposer > &_xComposer,
            const css::uno::Reference< css::beans::XPropertySet > &_xField,
            bool whenNotFound = false
        );

    /** is this column an aggregate?

        @param _xColumns collection of columns
            look for column sName in there
        @param _sName
            name of the column
        @param whenNotFound
            value returned when _sName is not in _xColumns
    */
    OOO_DLLPUBLIC_DBTOOLS bool isAggregateColumn(
            const css::uno::Reference< css::container::XNameAccess > &_xColumns,
            const OUString &_sName,
            bool whenNotFound = false
        );

    /** is this column an aggregate?

        @param _xColumn
    */
    OOO_DLLPUBLIC_DBTOOLS bool isAggregateColumn(
            const css::uno::Reference< css::beans::XPropertySet > &_xColumn
        );

}   // namespace dbtools

namespace connectivity
diff --git a/include/connectivity/filtermanager.hxx b/include/connectivity/filtermanager.hxx
index 41df1c6..45b58a6 100644
--- a/include/connectivity/filtermanager.hxx
+++ b/include/connectivity/filtermanager.hxx
@@ -61,14 +61,18 @@ namespace dbtools
        enum class FilterComponent
        {
            PublicFilter,     // The filter which is to be published as "Filter" property of the database component.
            LinkFilter        // The filter part which is implicitly created for a database component when connecting
            LinkFilter,       // The filter part which is implicitly created for a database component when connecting
                              // master and detail database components via column names.
            PublicHaving,     // the same, but should go in HAVING clause instead of WHERE clause
            LinkHaving
        };

    private:
        css::uno::Reference< css::beans::XPropertySet >   m_xComponentAggregate;
        OUString                                          m_aPublicFilterComponent;
        OUString                                          m_aPublicHavingComponent;
        OUString                                          m_aLinkFilterComponent;
        OUString                                          m_aLinkHavingComponent;
        bool                                              m_bApplyPublicFilter;

    public:
@@ -85,19 +89,21 @@ namespace dbtools
        void             setFilterComponent( FilterComponent _eWhich, const OUString& _rComponent );

        bool     isApplyPublicFilter( ) const { return m_bApplyPublicFilter; }
               void     setApplyPublicFilter( bool _bApply );
        void     setApplyPublicFilter( bool _bApply );

    private:
        /** retrieves a filter which is a conjunction of all single filter components
        */
        OUString         getComposedFilter( ) const;
        OUString         getComposedHaving( ) const;

        /** appends one filter component to the statement in our composer
        */
        static void      appendFilterComponent( OUStringBuffer& io_appendTo, const OUString& i_component );

        /// checks whether there is only one (or even no) non-empty filter component
        bool    isThereAtMostOneComponent( OUString& o_singleComponent ) const;
        bool    isThereAtMostOneFilterComponent( OUString& o_singleComponent ) const;
        bool    isThereAtMostOneHavingComponent( OUString& o_singleComponent ) const;
    };


diff --git a/include/connectivity/parameters.hxx b/include/connectivity/parameters.hxx
index 49eebb9..e5ba530 100644
--- a/include/connectivity/parameters.hxx
+++ b/include/connectivity/parameters.hxx
@@ -296,13 +296,19 @@ namespace dbtools
                the detail part denotes a column name. In such a case, an additional filter needs to be created,
                containing a new parameter.

            @param  _out_rAdditionalHavingComponents
                the additional having clause components which are required for master-detail relationships where
                the detail part denotes a column name. In such a case, an additional filter needs to be created,
                containing a new parameter.

            @precond
                <member>m_aMasterFields</member> and <member>m_aDetailFields</member> have the same length
        */
        void    classifyLinks(
                    const css::uno::Reference< css::container::XNameAccess >& _rxParentColumns,
                    const css::uno::Reference< css::container::XNameAccess >& _rxColumns,
                    ::std::vector< OUString >& _out_rAdditionalFilterComponents
                    ::std::vector< OUString >& _out_rAdditionalFilterComponents,
                    ::std::vector< OUString >& _out_rAdditionalHavingComponents
                );

        /** finalizes our <member>m_pOuterParameters</member> so that it can be used for