/* -*- 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 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 .
 */
 
#include <QueryDesignView.hxx>
#include <QueryTableView.hxx>
#include "QTableWindow.hxx"
#include <querycontroller.hxx>
#include <sqlbison.hxx>
#include <vcl/split.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>
#include <osl/diagnose.h>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <browserids.hxx>
#include "SelectionBrowseBox.hxx"
#include <strings.hrc>
#include <strings.hxx>
#include <comphelper/string.hxx>
#include <connectivity/dbtools.hxx>
#include <connectivity/dbexception.hxx>
#include <com/sun/star/sdbc/DataType.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/sdbc/ColumnValue.hpp>
#include <connectivity/PColumn.hxx>
#include "QTableConnection.hxx"
#include <ConnectionLineData.hxx>
#include "QTableConnectionData.hxx"
#include <core_resource.hxx>
#include <UITools.hxx>
#include <querycontainerwindow.hxx>
#include <unotools/localedatawrapper.hxx>
#include <unotools/syslocale.hxx>
#include <memory>
#include <set>
#include <string_view>
 
using namespace ::dbaui;
using namespace ::connectivity;
using namespace ::dbtools;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::sdbc;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::container;
 
// here we define our functions used in the anonymous namespace to get our header file smaller
// please look at the book LargeScale C++ to know why
namespace
{
    const char C_AND[] = " AND ";
    const char C_OR[] = " OR ";
 
    bool InsertJoin(    const OQueryDesignView* _pView,
                            const ::connectivity::OSQLParseNode *pNode);
 
    SqlParseError InstallFields(OQueryDesignView* _pView,
                                const ::connectivity::OSQLParseNode* pNode,
                                OJoinTableView::OTableWindowMap* pTabList );
 
    SqlParseError GetGroupCriteria( OQueryDesignView* _pView,
                                    OSelectionBrowseBox* _pSelectionBrw,
                                    const ::connectivity::OSQLParseNode* pSelectRoot );
 
    SqlParseError GetHavingCriteria(OQueryDesignView* _pView,
                                    OSelectionBrowseBox* _pSelectionBrw,
                                    const ::connectivity::OSQLParseNode* pSelectRoot,
                                    sal_uInt16& rLevel );
 
    SqlParseError GetOrderCriteria( OQueryDesignView* _pView,
                                    OSelectionBrowseBox* _pSelectionBrw,
                                    const ::connectivity::OSQLParseNode* pParseRoot );
 
    SqlParseError AddFunctionCondition(OQueryDesignView const * _pView,
                                    OSelectionBrowseBox* _pSelectionBrw,
                                    const ::connectivity::OSQLParseNode * pCondition,
                                    const sal_uInt16 nLevel,
                                    bool bHaving,
                                    bool _bAddOrOnOneLine);
 
    OUString quoteTableAlias(bool _bQuote, const OUString& _sAliasName, std::u16string_view _sQuote)
    {
        OUString sRet;
        if ( _bQuote && !_sAliasName.isEmpty() )
        {
            sRet = ::dbtools::quoteName(_sQuote,_sAliasName) + ".";
        }
        return sRet;
    }
    OUString getTableRange(const OQueryDesignView* _pView,const ::connectivity::OSQLParseNode* _pTableRef)
    {
        Reference< XConnection> xConnection = static_cast<OQueryController&>(_pView->getController()).getConnection();
        OUString sTableRange;
        if ( _pTableRef )
        {
            sTableRange = ::connectivity::OSQLParseNode::getTableRange(_pTableRef);
            if ( sTableRange.isEmpty() )
                _pTableRef->parseNodeToStr(sTableRange,xConnection,nullptr,false,false);
        }
        return sTableRange;
    }
    void insertConnection(const OQueryDesignView* _pView,const EJoinType& _eJoinType, const OTableFieldDescRef& _aDragLeft, const OTableFieldDescRef& _aDragRight, bool _bNatural = false)
    {
        OQueryTableView* pTableView = static_cast<OQueryTableView*>(_pView->getTableView());
        OQueryTableConnection* pConn = static_cast<OQueryTableConnection*>( pTableView->GetTabConn(static_cast<OTableWindow*>(_aDragLeft->GetTabWindow()),static_cast<OTableWindow*>(_aDragRight->GetTabWindow()),true));
 
        if ( !pConn )
        {
            auto xInfoData = std::make_shared<OQueryTableConnectionData>();
            xInfoData->InitFromDrag(_aDragLeft, _aDragRight);
            xInfoData->SetJoinType(_eJoinType);
 
            if ( _bNatural )
            {
                xInfoData->ResetConnLines();
                xInfoData->setNatural(_bNatural);
                try
                {
                    Reference<XNameAccess> xReferencedTableColumns(xInfoData->getReferencedTable()->getColumns());
                    for (auto& column : xInfoData->getReferencingTable()->getColumns()->getElementNames())
                    {
                        if (xReferencedTableColumns->hasByName(column))
                            xInfoData->AppendConnLine(column, column);
                    }
                }
                catch( const Exception& )
                {
                    DBG_UNHANDLED_EXCEPTION("dbaccess");
                }
            }
 
            ScopedVclPtrInstance< OQueryTableConnection > aInfo(pTableView, xInfoData);
            // Because OQueryTableConnection never takes ownership of the data passed to it, but only remembers the pointer,
            // this pointer to a local variable is not critical, as xInfoData and aInfo have the same lifetime
            pTableView->NotifyTabConnection( *aInfo );
        }
        else
        {
            OUString aSourceFieldName(_aDragLeft->GetField());
            OUString aDestFieldName(_aDragRight->GetField());
            // the connection could point on the other side
            if (pConn->GetSourceWin() == _aDragRight->GetTabWindow())
                std::swap(aSourceFieldName, aDestFieldName);
            pConn->GetData()->AppendConnLine( aSourceFieldName,aDestFieldName);
            pConn->UpdateLineList();
            // Modified-Flag
            //  SetModified();
            // and redraw
            pConn->RecalcLines();
                // for the following Invalidate, the new Connection must first be able
                // to determine its BoundingRect
            pConn->InvalidateConnection();
        }
    }
    OUString ParseCondition( OQueryController& rController
                                    ,const ::connectivity::OSQLParseNode* pCondition
                                    ,const OUString& _sDecimal
                                    ,const css::lang::Locale& _rLocale
                                    ,sal_uInt32 _nStartIndex)
    {
        OUString aCondition;
        Reference< XConnection> xConnection = rController.getConnection();
        if ( xConnection.is() )
        {
            sal_uInt32 nCount = pCondition->count();
            for(sal_uInt32 i = _nStartIndex ; i < nCount ; ++i)
                pCondition->getChild(i)->parseNodeToPredicateStr(aCondition,
                                xConnection,
                                rController.getNumberFormatter(),
                                _rLocale,
                                _sDecimal,
                                &rController.getParser().getContext());
        }
        return aCondition;
    }
    SqlParseError FillOuterJoins(OQueryDesignView const * _pView,
                                const ::connectivity::OSQLParseNode* pTableRefList)
    {
        SqlParseError eErrorCode = eOk;
        sal_uInt32 nCount = pTableRefList->count();
        bool bError = false;
        for (sal_uInt32 i=0; !bError && i < nCount; ++i)
        {
            const ::connectivity::OSQLParseNode* pParseNode = pTableRefList->getChild(i);
            const ::connectivity::OSQLParseNode* pJoinNode = nullptr;
 
            if ( SQL_ISRULE( pParseNode, qualified_join ) || SQL_ISRULE( pParseNode, joined_table ) || SQL_ISRULE( pParseNode, cross_union ) )
                pJoinNode = pParseNode;
            else if(    SQL_ISRULE(pParseNode,table_ref)
                    &&  pParseNode->count() == 4 ) // '{' SQL_TOKEN_OJ joined_table '}'
                pJoinNode = pParseNode->getChild(2);
 
            if ( pJoinNode )
            {
                if ( !InsertJoin(_pView,pJoinNode) )
                    bError = true;
            }
        }
        // check if error occurred
        if ( bError )
            eErrorCode = eIllegalJoin;
 
        return eErrorCode;
    }
 
    /** FillDragInfo fills the field description out of the table
    */
    SqlParseError FillDragInfo( const OQueryDesignView* _pView,
                            const ::connectivity::OSQLParseNode* pColumnRef,
                            OTableFieldDescRef const & _rDragInfo)
    {
        SqlParseError eErrorCode = eOk;
 
        bool bErg = false;
 
        OUString aTableRange,aColumnName;
        ::connectivity::OSQLParseTreeIterator& rParseIter = static_cast<OQueryController&>(_pView->getController()).getParseIterator();
        rParseIter.getColumnRange( pColumnRef, aColumnName, aTableRange );
 
        if ( !aTableRange.isEmpty() )
        {
            OQueryTableWindow*  pSTW = static_cast<OQueryTableView*>(_pView->getTableView())->FindTable( aTableRange );
            bErg = (pSTW && pSTW->ExistsField( aColumnName, _rDragInfo ) );
        }
        if ( !bErg )
        {
            sal_uInt16 nCntAccount;
            bErg = static_cast<OQueryTableView*>(_pView->getTableView())->FindTableFromField(aColumnName, _rDragInfo, nCntAccount);
            if ( !bErg )
                bErg = _pView->HasFieldByAliasName(aColumnName, _rDragInfo);
        }
        if ( !bErg )
        {
            eErrorCode = eColumnNotFound;
            OUString sError(DBA_RES(STR_QRY_COLUMN_NOT_FOUND));
            sError = sError.replaceFirst("$name$",aColumnName);
            _pView->getController().appendError( sError );
 
            try
            {
                Reference<XDatabaseMetaData> xMeta = _pView->getController().getConnection()->getMetaData();
                if ( xMeta.is() && xMeta->supportsMixedCaseQuotedIdentifiers() )
                    _pView->getController().appendError(DBA_RES(STR_QRY_CHECK_CASESENSITIVE));
            }
            catch(Exception&)
            {
            }
        }
 
        return eErrorCode;
    }
    OUString BuildJoinCriteria(  const Reference< XConnection>& _xConnection,
                                        const OConnectionLineDataVec* pLineDataList,
                                        const OQueryTableConnectionData* pData)
    {
        OUStringBuffer aCondition;
        if ( _xConnection.is() )
        {
            try
            {
                const Reference< XDatabaseMetaData >  xMetaData = _xConnection->getMetaData();
                const OUString aQuote = xMetaData->getIdentifierQuoteString();
 
                for (auto const& lineData : *pLineDataList)
                {
                    if(!aCondition.isEmpty())
                        aCondition.append(C_AND);
                    aCondition.append(
                        quoteTableAlias(true,pData->GetAliasName(JTCS_FROM),aQuote)
                        + ::dbtools::quoteName(aQuote, lineData->GetFieldName(JTCS_FROM) )
                        + " = "
                        + quoteTableAlias(true,pData->GetAliasName(JTCS_TO),aQuote)
                        + ::dbtools::quoteName(aQuote, lineData->GetFieldName(JTCS_TO) ));
                }
            }
            catch(SQLException&)
            {
                OSL_FAIL("Failure while building Join criteria!");
            }
        }
 
        return aCondition.makeStringAndClear();
    }
    /** JoinCycle looks for a join cycle and append it to the string
        @param  _xConnection    the connection
        @param  _pEntryConn     the table connection which holds the data
        @param  _pEntryTabTo    the corresponding table window
        @param  _rJoin          the String which will contain the resulting string
    */
    void JoinCycle( const Reference< XConnection>& _xConnection,
                    OQueryTableConnection* _pEntryConn,
                    const OQueryTableWindow* _pEntryTabTo,
                    OUString& _rJoin )
    {
        assert(_pEntryConn && "TableConnection can not be null!");
 
        OQueryTableConnectionData* pData = static_cast< OQueryTableConnectionData*>(_pEntryConn->GetData().get());
        if ( !(pData->GetJoinType() != INNER_JOIN && _pEntryTabTo->ExistsAVisitedConn()) )
            return;
 
        bool bBrace = false;
        if(_rJoin.endsWith(")"))
        {
            bBrace = true;
            _rJoin = _rJoin.replaceAt(_rJoin.getLength()-1,1, u" ");
        }
        _rJoin += C_AND + BuildJoinCriteria(_xConnection,&pData->GetConnLineDataList(),pData);
        if(bBrace)
            _rJoin += ")";
        _pEntryConn->SetVisited(true);
    }
    OUString BuildTable( const Reference< XConnection>& _xConnection,
                                const OQueryTableWindow* pEntryTab,
                                bool _bForce = false
                                )
    {
        OUString aDBName(pEntryTab->GetComposedName());
 
        if( _xConnection.is() )
        {
            try
            {
                Reference< XDatabaseMetaData >  xMetaData = _xConnection->getMetaData();
 
                OUString sCatalog, sSchema, sTable;
                ::dbtools::qualifiedNameComponents( xMetaData, aDBName, sCatalog, sSchema, sTable, ::dbtools::EComposeRule::InDataManipulation );
                OUString aTableListStr = ::dbtools::composeTableNameForSelect( _xConnection, sCatalog, sSchema, sTable );
 
                OUString aQuote = xMetaData->getIdentifierQuoteString();
                if ( _bForce || isAppendTableAliasEnabled( _xConnection ) || pEntryTab->GetAliasName() != aDBName )
                {
                    aTableListStr += " ";
                    if ( generateAsBeforeTableAlias( _xConnection ) )
                        aTableListStr += "AS ";
                    aTableListStr += ::dbtools::quoteName( aQuote, pEntryTab->GetAliasName() );
                }
                aDBName = aTableListStr;
            }
            catch(const SQLException&)
            {
                DBG_UNHANDLED_EXCEPTION("dbaccess");
            }
        }
        return aDBName;
    }
    OUString BuildJoin(  const Reference< XConnection>& _xConnection,
                                const OUString& rLh,
                                std::u16string_view rRh,
                                const OQueryTableConnectionData* pData)
    {
 
        OUString aErg(rLh);
        if ( pData->isNatural() && pData->GetJoinType() != CROSS_JOIN )
            aErg += " NATURAL ";
        switch(pData->GetJoinType())
        {
            case LEFT_JOIN:
                aErg += " LEFT OUTER ";
                break;
            case RIGHT_JOIN:
                aErg += " RIGHT OUTER ";
                break;
            case CROSS_JOIN:
                OSL_ENSURE(!pData->isNatural(),"OQueryDesignView::BuildJoin: This should not happen!");
                aErg += " CROSS ";
                break;
            case INNER_JOIN:
                OSL_ENSURE(pData->isNatural(),"OQueryDesignView::BuildJoin: This should not happen!");
                aErg += " INNER ";
                break;
            default:
                aErg += " FULL OUTER ";
                break;
        }
        aErg += OUString::Concat("JOIN ") + rRh;
        if ( CROSS_JOIN != pData->GetJoinType() && !pData->isNatural() )
        {
            aErg += " ON " + BuildJoinCriteria(_xConnection,&pData->GetConnLineDataList(),pData);
        }
 
        return aErg;
    }
    OUString BuildJoin(  const Reference< XConnection>& _xConnection,
                                const OQueryTableWindow* pLh,
                                const OQueryTableWindow* pRh,
                                const OQueryTableConnectionData* pData
                                )
    {
        bool bForce = pData->GetJoinType() == CROSS_JOIN || pData->isNatural();
        return BuildJoin(_xConnection,BuildTable(_xConnection,pLh,bForce),BuildTable(_xConnection,pRh,bForce),pData);
    }
    OUString BuildJoin(  const Reference< XConnection>& _xConnection,
                                const OUString &rLh,
                                const OQueryTableWindow* pRh,
                                const OQueryTableConnectionData* pData
                                )
    {
        return BuildJoin(_xConnection,rLh,BuildTable(_xConnection,pRh),pData);
    }
    OUString BuildJoin(  const Reference< XConnection>& _xConnection,
                                const OQueryTableWindow* pLh,
                                const OUString &rRh,
                                const OQueryTableConnectionData* pData
                                )
    {
        // strict ANSI SQL:
        // - does not support any bracketing of JOINS
        // - supports nested joins only in the LEFT HAND SIDE
        // In this case, we are trying to build a join with a nested join
        // in the right hand side.
        // So switch the direction of the join and both hand sides.
        OQueryTableConnectionData data(*pData);
        switch (data.GetJoinType())
        {
        case LEFT_JOIN:
            data.SetJoinType(RIGHT_JOIN);
            break;
        case RIGHT_JOIN:
            data.SetJoinType(LEFT_JOIN);
            break;
        default:
            // the other join types are symmetric, so nothing to change
            break;
        }
        return BuildJoin(_xConnection, rRh, BuildTable(_xConnection,pLh), &data);
    }
    void addConnectionTableNames( const Reference< XConnection>& _xConnection,
                                  const OQueryTableConnection* const pEntryConn,
                                  std::set<OUString> &_rTableNames )
    {
            // insert tables into table list to avoid double entries
            const OQueryTableWindow* const pEntryTabFrom = static_cast<OQueryTableWindow*>(pEntryConn->GetSourceWin());
            const OQueryTableWindow* const pEntryTabTo = static_cast<OQueryTableWindow*>(pEntryConn->GetDestWin());
            _rTableNames.insert(BuildTable(_xConnection,pEntryTabFrom));
            _rTableNames.insert(BuildTable(_xConnection,pEntryTabTo));
    }
    void GetNextJoin(   const Reference< XConnection>& _xConnection,
                        OQueryTableConnection* pEntryConn,
                        OQueryTableWindow const * pEntryTabTo,
                        OUString &aJoin,
                        std::set<OUString> &_rTableNames)
    {
        OQueryTableConnectionData* pEntryConnData = static_cast<OQueryTableConnectionData*>(pEntryConn->GetData().get());
        if ( pEntryConnData->GetJoinType() == INNER_JOIN && !pEntryConnData->isNatural() )
            return;
 
        if(aJoin.isEmpty())
        {
            addConnectionTableNames(_xConnection, pEntryConn, _rTableNames);
            OQueryTableWindow* pEntryTabFrom = static_cast<OQueryTableWindow*>(pEntryConn->GetSourceWin());
            aJoin = BuildJoin(_xConnection,pEntryTabFrom,pEntryTabTo,pEntryConnData);
        }
        else if(pEntryTabTo == pEntryConn->GetDestWin())
        {
            addConnectionTableNames(_xConnection, pEntryConn, _rTableNames);
            aJoin = BuildJoin(_xConnection,aJoin,pEntryTabTo,pEntryConnData);
        }
        else if(pEntryTabTo == pEntryConn->GetSourceWin())
        {
            addConnectionTableNames(_xConnection, pEntryConn, _rTableNames);
            aJoin = BuildJoin(_xConnection,pEntryTabTo,aJoin,pEntryConnData);
        }
 
        pEntryConn->SetVisited(true);
 
        // first search for the "to" window
        const auto& rConnections = pEntryConn->GetParent()->getTableConnections();
        bool bFound = false;
        for (auto const& connection : rConnections)
        {
            OQueryTableConnection* pNext = static_cast<OQueryTableConnection*>(connection.get());
            if(!pNext->IsVisited() && (pNext->GetSourceWin() == pEntryTabTo || pNext->GetDestWin() == pEntryTabTo))
            {
                OQueryTableWindow* pEntryTab = pNext->GetSourceWin() == pEntryTabTo ? static_cast<OQueryTableWindow*>(pNext->GetDestWin()) : static_cast<OQueryTableWindow*>(pNext->GetSourceWin());
                // exists there a connection to a OQueryTableWindow that holds a connection that has been already visited
                JoinCycle(_xConnection,pNext,pEntryTab,aJoin);
                if(!pNext->IsVisited())
                    GetNextJoin(_xConnection, pNext, pEntryTab, aJoin, _rTableNames);
                bFound = true;
            }
        }
 
        // when nothing found look for the "from" window
        if(bFound)
            return;
 
        OQueryTableWindow* pEntryTabFrom = static_cast<OQueryTableWindow*>(pEntryConn->GetSourceWin());
        for (auto const& connection : rConnections)
        {
            OQueryTableConnection* pNext = static_cast<OQueryTableConnection*>(connection.get());
            if(!pNext->IsVisited() && (pNext->GetSourceWin() == pEntryTabFrom || pNext->GetDestWin() == pEntryTabFrom))
            {
                OQueryTableWindow* pEntryTab = pNext->GetSourceWin() == pEntryTabFrom ? static_cast<OQueryTableWindow*>(pNext->GetDestWin()) : static_cast<OQueryTableWindow*>(pNext->GetSourceWin());
                // exists there a connection to a OQueryTableWindow that holds a connection that has been already visited
                JoinCycle(_xConnection,pNext,pEntryTab,aJoin);
                if(!pNext->IsVisited())
                    GetNextJoin(_xConnection, pNext, pEntryTab, aJoin, _rTableNames);
            }
        }
    }
    SqlParseError InsertJoinConnection( const OQueryDesignView* _pView,
                                    const ::connectivity::OSQLParseNode *pNode,
                                    const EJoinType& _eJoinType,
                                    const ::connectivity::OSQLParseNode *pLeftTable,
                                    const ::connectivity::OSQLParseNode *pRightTable)
    {
        SqlParseError eErrorCode = eOk;
        if (pNode->count() == 3 &&  // statement between brackets
            SQL_ISPUNCTUATION(pNode->getChild(0),"(") &&
            SQL_ISPUNCTUATION(pNode->getChild(2),")"))
        {
            eErrorCode = InsertJoinConnection(_pView,pNode->getChild(1), _eJoinType,pLeftTable,pRightTable);
        }
        else if (SQL_ISRULEOR2(pNode,search_condition,boolean_term) &&          // AND/OR-joints:
                 pNode->count() == 3)
        {
            // only allow AND joints
            if (!SQL_ISTOKEN(pNode->getChild(1),AND))
                eErrorCode = eIllegalJoinCondition;
            else if ( eOk == (eErrorCode = InsertJoinConnection(_pView,pNode->getChild(0), _eJoinType,pLeftTable,pRightTable)) )
                    eErrorCode = InsertJoinConnection(_pView,pNode->getChild(2), _eJoinType,pLeftTable,pRightTable);
        }
        else if (SQL_ISRULE(pNode,comparison_predicate))
        {
            // only the comparison of columns is allowed
            OSL_ENSURE(pNode->count() == 3,"OQueryDesignView::InsertJoinConnection: Error in Parse Tree");
            if (!(SQL_ISRULE(pNode->getChild(0),column_ref) &&
                  SQL_ISRULE(pNode->getChild(2),column_ref) &&
                   pNode->getChild(1)->getNodeType() == SQLNodeType::Equal))
            {
                OUString sError(DBA_RES(STR_QRY_JOIN_COLUMN_COMPARE));
                _pView->getController().appendError( sError );
                return eIllegalJoin;
            }
 
            OTableFieldDescRef aDragLeft  = new OTableFieldDesc();
            OTableFieldDescRef aDragRight = new OTableFieldDesc();
            eErrorCode = FillDragInfo(_pView,pNode->getChild(0),aDragLeft);
            if ( eOk != eErrorCode )
                return eErrorCode;
            eErrorCode = FillDragInfo(_pView,pNode->getChild(2),aDragRight);
            if ( eOk != eErrorCode )
                return eErrorCode;
 
            if ( pLeftTable )
            {
                OQueryTableWindow*  pLeftWindow = static_cast<OQueryTableView*>(_pView->getTableView())->FindTable( getTableRange(_pView,pLeftTable->getByRule(OSQLParseNode::table_ref) ));
                if ( pLeftWindow == aDragLeft->GetTabWindow() )
                    insertConnection(_pView,_eJoinType,aDragLeft,aDragRight);
                else
                    insertConnection(_pView,_eJoinType,aDragRight,aDragLeft);
            }
            else
                insertConnection(_pView,_eJoinType,aDragLeft,aDragRight);
        }
        else
            eErrorCode = eIllegalJoin;
        return eErrorCode;
    }
    bool GetInnerJoinCriteria(  const OQueryDesignView* _pView,
                                    const ::connectivity::OSQLParseNode *pCondition)
    {
        return InsertJoinConnection(_pView,pCondition, INNER_JOIN,nullptr,nullptr) != eOk;
    }
    OUString GenerateSelectList( const OQueryDesignView* _pView,
                                        OTableFields&   _rFieldList,
                                        bool bAlias)
    {
        Reference< XConnection> xConnection = static_cast<OQueryController&>(_pView->getController()).getConnection();
        if ( !xConnection.is() )
            return OUString();
 
        OUStringBuffer aTmpStr,aFieldListStr;
 
        bool bAsterisk = false;
        int nVis = 0;
        for (auto const& field : _rFieldList)
        {
            if ( field->IsVisible() )
            {
                if ( field->GetField().toChar() == '*' )
                    bAsterisk = true;
                ++nVis;
            }
        }
        if(nVis == 1)
            bAsterisk = false;
 
        try
        {
            const Reference< XDatabaseMetaData >  xMetaData = xConnection->getMetaData();
            const OUString aQuote = xMetaData->getIdentifierQuoteString();
 
            OJoinTableView::OTableWindowMap& rTabList = _pView->getTableView()->GetTabWinMap();
 
            for (auto const& field : _rFieldList)
            {
                OUString rFieldName = field->GetField();
                if ( !rFieldName.isEmpty() && field->IsVisible() )
                {
                    aTmpStr = "";
                    const OUString rAlias = field->GetAlias();
                    const OUString rFieldAlias = field->GetFieldAlias();
 
                    aTmpStr.append(quoteTableAlias((bAlias || bAsterisk),rAlias,aQuote));
 
                    // if we have a none numeric field, the table alias could be in the name
                    // otherwise we are not allowed to do this (e.g. 0.1 * PRICE )
                    if  ( !field->isOtherFunction() )
                    {
                        // we have to look if we have alias.* here but before we have to check if the column doesn't already exist
                        OTableFieldDescRef  aInfo = new OTableFieldDesc();
                        for (auto const& table : rTabList)
                        {
                            OQueryTableWindow* pTabWin = static_cast<OQueryTableWindow*>(table.second.get());
 
                            if ( pTabWin->ExistsField( rFieldName, aInfo ) )
                            {
                                rFieldName = aInfo->GetField();
                                break;
                            }
                        }
                        if ( ( rFieldName.toChar() != '*' ) && ( rFieldName.indexOf( aQuote ) == -1 ) )
                        {
                            OSL_ENSURE(!field->GetTable().isEmpty(),"No table field name!");
                            aTmpStr.append(::dbtools::quoteName(aQuote, rFieldName));
                        }
                        else
                            aTmpStr.append(rFieldName);
                    }
                    else
                        aTmpStr.append(rFieldName);
 
                    if  ( field->isAggregateFunction() )
                    {
                        OSL_ENSURE(!field->GetFunction().isEmpty(),"Function name must not be empty! ;-(");
                        aTmpStr = field->GetFunction() + "(" + aTmpStr.makeStringAndClear() + ")";
                    }
 
                    if (!rFieldAlias.isEmpty()                         &&
                        (rFieldName.toChar() != '*'                     ||
                        field->isNumericOrAggregateFunction()      ||
                        field->isOtherFunction()))
                    {
                        aTmpStr.append(" AS " + ::dbtools::quoteName(aQuote, rFieldAlias));
                    }
                    aFieldListStr.append(aTmpStr);
                    aTmpStr.setLength(0);
                    aFieldListStr.append(", ");
                }
            }
            if(!aFieldListStr.isEmpty())
                aFieldListStr.setLength(aFieldListStr.getLength()-2);
        }
        catch(SQLException&)
        {
            OSL_FAIL("Failure while building select list!");
        }
        return aFieldListStr.makeStringAndClear();
    }
    bool GenerateCriterias( OQueryDesignView const * _pView,
                                OUStringBuffer& rRetStr,
                                OUStringBuffer& rHavingStr,
                                OTableFields& _rFieldList,
                                bool bMulti )
    {
        Reference< XConnection> xConnection = static_cast<OQueryController&>(_pView->getController()).getConnection();
        if(!xConnection.is())
            return false;
 
        OUString aFieldName,aCriteria,aWhereStr,aHavingStr,aWork/*,aOrderStr*/;
        // print line by line joined with AND
        sal_uInt16 nMaxCriteria = 0;
        for (auto const& field : _rFieldList)
        {
            nMaxCriteria = std::max<sal_uInt16>(nMaxCriteria,static_cast<sal_uInt16>(field->GetCriteria().size()));
        }
        try
        {
            const Reference< XDatabaseMetaData >  xMetaData = xConnection->getMetaData();
            const OUString aQuote = xMetaData->getIdentifierQuoteString();
            const IParseContext& rContext = static_cast<OQueryController&>(_pView->getController()).getParser().getContext();
            // * must not contain a filter : have I already shown the correct warning ?
            bool bCritsOnAsteriskWarning = false;        // ** TMFS **
 
            for (sal_uInt16 i=0 ; i < nMaxCriteria ; i++)
            {
                aHavingStr.clear();
                aWhereStr.clear();
 
                for (auto const& field : _rFieldList)
                {
                    aFieldName = field->GetField();
 
                    if (aFieldName.isEmpty())
                        continue;
                    aCriteria = field->GetCriteria( i );
                    if ( !aCriteria.isEmpty() )
                    {
                        // * is not allowed to contain any filter, only when used in combination an aggregate function
                        if ( aFieldName.toChar() == '*' && field->isNoneFunction() )
                        {
                            // only show the messagebox the first time
                            if (!bCritsOnAsteriskWarning)
                            {
                                std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(_pView->GetFrameWeld(),
                                                                          VclMessageType::Warning, VclButtonsType::Ok,
                                                                          DBA_RES(STR_QRY_CRITERIA_ON_ASTERISK)));
                                xBox->run();
                            }
                            bCritsOnAsteriskWarning = true;
                            continue;
                        }
                        aWork = quoteTableAlias(bMulti,field->GetAlias(),aQuote);
 
                        if ( (field->GetFunctionType() & (FKT_OTHER|FKT_NUMERIC)) || (aFieldName.toChar() == '*') )
                            aWork += aFieldName;
                        else
                            aWork += ::dbtools::quoteName(aQuote, aFieldName);
 
                        if ( field->isAggregateFunction() || field->IsGroupBy() )
                        {
                            if (aHavingStr.isEmpty())            // no more criteria
                                aHavingStr += "(";               // bracket
                            else
                                aHavingStr += C_AND;
 
                            if ( field->isAggregateFunction() )
                            {
                                OSL_ENSURE(!field->GetFunction().isEmpty(),"No function name for aggregate given!");
                                aHavingStr += field->GetFunction() + "(" + aWork + ")";       // bracket
                            }
                            else
                                aHavingStr += aWork;
 
                            OUString aErrorMsg;
                            Reference<XPropertySet> xColumn;
                            std::unique_ptr< ::connectivity::OSQLParseNode> pParseNode(_pView->getPredicateTreeFromEntry(field,aCriteria,aErrorMsg,xColumn));
                            if (pParseNode)
                            {
                                if (bMulti && !(field->isOtherFunction() || (aFieldName.toChar() == '*')))
                                    pParseNode->replaceNodeValue(field->GetAlias(),aFieldName);
                                OUString sHavingStr = aHavingStr;
 
                                sal_uInt32 nCount = pParseNode->count();
                                for( sal_uInt32 node = 1 ; node < nCount ; ++node)
                                    pParseNode->getChild(node)->parseNodeToStr( sHavingStr,
                                                                xConnection,
                                                                &rContext,
                                                                false,
                                                                !field->isOtherFunction());
                                aHavingStr = sHavingStr;
                            }
                            else
                                aHavingStr += aCriteria;
                        }
                        else
                        {
                            if ( aWhereStr.isEmpty() )           // no more criteria
                                aWhereStr += "(";                // bracket
                            else
                                aWhereStr += C_AND;
 
                            aWhereStr += " ";
                            // aCriteria could have some German numbers so I have to be sure here
                            OUString aErrorMsg;
                            Reference<XPropertySet> xColumn;
                            std::unique_ptr< ::connectivity::OSQLParseNode> pParseNode( _pView->getPredicateTreeFromEntry(field,aCriteria,aErrorMsg,xColumn));
                            if (pParseNode)
                            {
                                if (bMulti && !(field->isOtherFunction() || (aFieldName.toChar() == '*')))
                                    pParseNode->replaceNodeValue(field->GetAlias(),aFieldName);
                                OUString aWhere = aWhereStr;
                                pParseNode->parseNodeToStr( aWhere,
                                                            xConnection,
                                                            &rContext,
                                                            false,
                                                            !field->isOtherFunction() );
                                aWhereStr = aWhere;
                            }
                            else
                            {
                                aWhereStr += aWork + "=" + aCriteria;
                            }
                        }
                    }
                    // only once for each field
                    else if ( !i && field->isCondition() )
                    {
                        if (aWhereStr.isEmpty())         // no more criteria
                            aWhereStr += "(";            // bracket
                        else
                            aWhereStr += C_AND;
                        aWhereStr += field->GetField();
                    }
                }
                if (!aWhereStr.isEmpty())
                {
                    aWhereStr += ")";                          // close bracket for the AND branch
                    if (!rRetStr.isEmpty())                            // are there conditions on the field?
                        rRetStr.append(C_OR);
                    else                                        // open bracket for the OR branch
                        rRetStr.append('(');
                    rRetStr.append(aWhereStr);
                }
                if (!aHavingStr.isEmpty())
                {
                    aHavingStr +=  ")";                        // close bracket for the AND branch
                    if (!rHavingStr.isEmpty())                         // are there conditions on the field?
                        rHavingStr.append(C_OR);
                    else                                        // Open bracket for the OR branch
                        rHavingStr.append('(');
                    rHavingStr.append(aHavingStr);
                }
            }
 
            if (!rRetStr.isEmpty())
                rRetStr.append(')');                               // close bracket for the OR branch
            if (!rHavingStr.isEmpty())
                rHavingStr.append(')');                                // close bracket for the OR branch
        }
        catch(SQLException&)
        {
            OSL_FAIL("Failure while building where clause!");
        }
        return true;
    }
    SqlParseError GenerateOrder(    OQueryDesignView const * _pView,
                                    OTableFields& _rFieldList,
                                    bool bMulti,
                                    OUString& _rsRet)
    {
        const OQueryController& rController = static_cast<OQueryController&>(_pView->getController());
        const Reference< XConnection>& xConnection = rController.getConnection();
        if ( !xConnection.is() )
            return eNoConnection;
 
        SqlParseError eErrorCode = eOk;
 
        OUString aColumnName;
        OUString aWorkStr;
        try
        {
            const bool bColumnAliasInOrderBy = rController.getSdbMetaData().supportsColumnAliasInOrderBy();
            Reference< XDatabaseMetaData >  xMetaData = xConnection->getMetaData();
            OUString aQuote = xMetaData->getIdentifierQuoteString();
            // * must not contain filter - have I already shown the warning?
            bool bCritsOnAsteriskWarning = false;        // ** TMFS **
            for (auto const& field : _rFieldList)
            {
                EOrderDir eOrder = field->GetOrderDir();
                // only create a sort expression when the table name and the sort criteria are defined
                // otherwise they will be built in GenerateCriteria
                if ( eOrder != ORDER_NONE )
                {
                    aColumnName = field->GetField();
                    if(aColumnName.toChar() == '*')
                    {
                        // only show the  MessageBox the first time
                        if (!bCritsOnAsteriskWarning)
                        {
                            std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(_pView->GetFrameWeld(),
                                                                      VclMessageType::Warning, VclButtonsType::Ok,
                                                                      DBA_RES(STR_QRY_ORDERBY_ON_ASTERISK)));
                            xBox->run();
                        }
                        bCritsOnAsteriskWarning = true;
                        continue;
                    }
 
                    if ( bColumnAliasInOrderBy && !field->GetFieldAlias().isEmpty() )
                    {
                        aWorkStr += ::dbtools::quoteName(aQuote, field->GetFieldAlias());
                    }
                    else if ( field->isNumericOrAggregateFunction() )
                    {
                        OSL_ENSURE(!field->GetFunction().isEmpty(),"Function name cannot be empty! ;-(");
                        aWorkStr += field->GetFunction() + "("
                            + quoteTableAlias(
                                bMulti, field->GetAlias(), aQuote);
                        // only quote column name when we don't have a numeric
                        if ( field->isNumeric() )
                            aWorkStr += aColumnName;
                        else
                            aWorkStr += ::dbtools::quoteName(aQuote, aColumnName);
 
                        aWorkStr += ")";
                    }
                    else if ( field->isOtherFunction() )
                    {
                        aWorkStr += aColumnName;
                    }
                    else
                    {
                        aWorkStr += quoteTableAlias(bMulti,field->GetAlias(),aQuote) + ::dbtools::quoteName(aQuote, aColumnName);
                    }
                    aWorkStr += OUString::Concat(" ") + o3tl::getToken( u";ASC;DESC", static_cast<sal_uInt16>(eOrder), ';' ) + ",";
                }
            }
 
            aWorkStr = comphelper::string::stripEnd(aWorkStr, ',');
 
            if ( !aWorkStr.isEmpty() )
            {
                const sal_Int32 nMaxOrder = xMetaData->getMaxColumnsInOrderBy();
                if ( nMaxOrder && nMaxOrder < comphelper::string::getTokenCount(aWorkStr, ',') )
                    eErrorCode = eStatementTooLong;
                else
                {
                    _rsRet = " ORDER BY " + aWorkStr;
                }
            }
        }
        catch(SQLException&)
        {
            OSL_FAIL("Failure while building group by!");
        }
 
        return eErrorCode;
    }
 
    void GenerateInnerJoinCriterias(const Reference< XConnection>& _xConnection,
                                    OUString& _rJoinCrit,
                                    const std::vector<VclPtr<OTableConnection> >& _rConnList)
    {
        for (auto const& connection : _rConnList)
        {
            const OQueryTableConnection* pEntryConn = static_cast<const OQueryTableConnection*>(connection.get());
            OQueryTableConnectionData* pEntryConnData = static_cast<OQueryTableConnectionData*>(pEntryConn->GetData().get());
            if ( pEntryConnData->GetJoinType() == INNER_JOIN && !pEntryConnData->isNatural() )
            {
                if(!_rJoinCrit.isEmpty())
                    _rJoinCrit += C_AND;
                _rJoinCrit += BuildJoinCriteria(_xConnection,&pEntryConnData->GetConnLineDataList(),pEntryConnData);
            }
        }
    }
    void searchAndAppendName(const Reference< XConnection>& _xConnection,
                             const OQueryTableWindow* _pTableWindow,
                             std::set<OUString>& _rTableNames,
                             OUString& _rsTableListStr
                             )
    {
        OUString sTabName(BuildTable(_xConnection,_pTableWindow));
 
        if(_rTableNames.insert(sTabName).second)
        {
            _rsTableListStr += sTabName + ",";
        }
    }
    OUString GenerateFromClause( const Reference< XConnection>& _xConnection,
                                        const OQueryTableView::OTableWindowMap* pTabList,
                                        const std::vector<VclPtr<OTableConnection> >& rConnList
                                        )
    {
 
        OUString aTableListStr;
        // used to avoid putting a table twice in FROM clause
        std::set<OUString> aTableNames;
 
        // generate outer join clause in from
        if(!rConnList.empty())
        {
            std::map<OTableWindow*,sal_Int32> aConnectionCount;
            auto aEnd = rConnList.end();
            for (auto const& connection : rConnList)
            {
                static_cast<OQueryTableConnection*>(connection.get())->SetVisited(false);
                ++aConnectionCount[connection->GetSourceWin()];
                ++aConnectionCount[connection->GetDestWin()];
            }
            std::multimap<sal_Int32 , OTableWindow*> aMulti;
            for (auto const& elem : aConnectionCount)
            {
                aMulti.emplace(elem.second,elem.first);
            }
 
            const bool bUseEscape = ::dbtools::getBooleanDataSourceSetting( _xConnection, PROPERTY_OUTERJOINESCAPE );
            std::multimap<sal_Int32 , OTableWindow*>::const_reverse_iterator aRIter = aMulti.rbegin();
            std::multimap<sal_Int32 , OTableWindow*>::const_reverse_iterator aREnd = aMulti.rend();
            for(;aRIter != aREnd;++aRIter)
            {
                auto aConIter = aRIter->second->getTableView()->getTableConnections(aRIter->second);
                for(;aConIter != aEnd;++aConIter)
                {
                    OQueryTableConnection* pEntryConn = static_cast<OQueryTableConnection*>((*aConIter).get());
                    if(!pEntryConn->IsVisited() && pEntryConn->GetSourceWin() == aRIter->second )
                    {
                        OUString aJoin;
                        GetNextJoin(_xConnection,
                                    pEntryConn,
                                    static_cast<OQueryTableWindow*>(pEntryConn->GetDestWin()),
                                    aJoin,
                                    aTableNames);
 
                        if(!aJoin.isEmpty())
                        {
                            OUString aStr;
                            switch(static_cast<OQueryTableConnectionData*>(pEntryConn->GetData().get())->GetJoinType())
                            {
                                case LEFT_JOIN:
                                case RIGHT_JOIN:
                                case FULL_JOIN:
                                    {
                                        // create outer join
                                        if ( bUseEscape )
                                            aStr += "{ oj ";
                                        aStr += aJoin;
                                        if ( bUseEscape )
                                            aStr += " }";
                                    }
                                    break;
                                default:
                                    aStr += aJoin;
                                    break;
                            }
                            aStr += ",";
                            aTableListStr += aStr;
                        }
                    }
                }
            }
 
            // and now all inner joins
            // these are implemented as
            // "FROM tbl1, tbl2 WHERE tbl1.col1=tlb2.col2"
            // rather than
            // "FROM tbl1 INNER JOIN tbl2 ON tbl1.col1=tlb2.col2"
            for (auto const& connection : rConnList)
            {
                OQueryTableConnection* pEntryConn = static_cast<OQueryTableConnection*>(connection.get());
                if(!pEntryConn->IsVisited())
                {
                    searchAndAppendName(_xConnection,
                                        static_cast<OQueryTableWindow*>(pEntryConn->GetSourceWin()),
                                        aTableNames,
                                        aTableListStr);
 
                    searchAndAppendName(_xConnection,
                                        static_cast<OQueryTableWindow*>(pEntryConn->GetDestWin()),
                                        aTableNames,
                                        aTableListStr);
                }
            }
        }
        // all tables that haven't a connection to anyone
        for (auto const& table : *pTabList)
        {
            const OQueryTableWindow* pEntryTab = static_cast<const OQueryTableWindow*>(table.second.get());
            if(!pEntryTab->ExistsAConn())
            {
                aTableListStr += BuildTable(_xConnection,pEntryTab) + ",";
            }
        }
 
        if(!aTableListStr.isEmpty())
            aTableListStr = aTableListStr.replaceAt(aTableListStr.getLength()-1,1, u"" );
        return aTableListStr;
    }
    OUString GenerateGroupBy(const OQueryDesignView* _pView,OTableFields& _rFieldList, bool bMulti )
    {
        OQueryController& rController = static_cast<OQueryController&>(_pView->getController());
        const Reference< XConnection> xConnection = rController.getConnection();
        if(!xConnection.is())
            return OUString();
 
        std::map< OUString,bool> aGroupByNames;
 
        OUString aGroupByStr;
        try
        {
            const Reference< XDatabaseMetaData >  xMetaData = xConnection->getMetaData();
            const OUString aQuote = xMetaData->getIdentifierQuoteString();
 
            for (auto const& field : _rFieldList)
            {
                if ( field->IsGroupBy() )
                {
                    OSL_ENSURE(!field->GetField().isEmpty(),"No Field Name available!;-(");
                    OUString sGroupByPart = quoteTableAlias(bMulti,field->GetAlias(),aQuote);
 
                    // only quote the field name when it isn't calculated
                    if ( field->isNoneFunction() )
                    {
                        sGroupByPart += ::dbtools::quoteName(aQuote, field->GetField());
                    }
                    else
                    {
                        OUString aTmp = field->GetField();
                        OUString aErrorMsg;
                        Reference<XPropertySet> xColumn;
                        std::unique_ptr< ::connectivity::OSQLParseNode> pParseNode(_pView->getPredicateTreeFromEntry(field,aTmp,aErrorMsg,xColumn));
                        if (pParseNode)
                        {
                            OUString sGroupBy;
                            pParseNode->getChild(0)->parseNodeToStr(    sGroupBy,
                                                        xConnection,
                                                        &rController.getParser().getContext(),
                                                        false,
                                                        !field->isOtherFunction());
                            sGroupByPart += sGroupBy;
                        }
                        else
                            sGroupByPart += field->GetField();
                    }
                    if ( aGroupByNames.find(sGroupByPart) == aGroupByNames.end() )
                    {
                        aGroupByNames.emplace(sGroupByPart,true);
                        aGroupByStr += sGroupByPart + ",";
                    }
                }
            }
            if ( !aGroupByStr.isEmpty() )
            {
                aGroupByStr = aGroupByStr.replaceAt(aGroupByStr.getLength()-1,1, u" " );
                aGroupByStr = " GROUP BY " + aGroupByStr;
            }
        }
        catch(SQLException&)
        {
            OSL_FAIL("Failure while building group by!");
        }
        return aGroupByStr;
    }
    SqlParseError GetORCriteria(OQueryDesignView* _pView,
                                OSelectionBrowseBox* _pSelectionBrw,
                                const ::connectivity::OSQLParseNode * pCondition,
                                sal_uInt16& nLevel ,
                                bool bHaving = false,
                                bool bAddOrOnOneLine = false);
    SqlParseError GetSelectionCriteria( OQueryDesignView* _pView,
                                        OSelectionBrowseBox* _pSelectionBrw,
                                        const ::connectivity::OSQLParseNode* pNode,
                                        sal_uInt16& rLevel )
    {
        if (!pNode || !SQL_ISRULE(pNode, select_statement))
            return eNoSelectStatement;
 
        // nyi: more checking for the correct structure!
        pNode = pNode->getChild(3)->getChild(1);
        // no where clause found
        if (!pNode || pNode->isLeaf())
            return eOk;
 
        // Next free sentence...
        SqlParseError eErrorCode = eOk;
        ::connectivity::OSQLParseNode * pCondition = pNode->getChild(1);
        if ( pCondition ) // no where clause
        {
            // now we have to check the other conditions
            // first make the logical easier
            ::connectivity::OSQLParseNode::negateSearchCondition(pCondition);
            ::connectivity::OSQLParseNode *pNodeTmp = pNode->getChild(1);
 
            ::connectivity::OSQLParseNode::disjunctiveNormalForm(pNodeTmp);
            pNodeTmp = pNode->getChild(1);
            ::connectivity::OSQLParseNode::absorptions(pNodeTmp);
            pNodeTmp = pNode->getChild(1);
            // compress sort the criteria @see https://bz.apache.org/ooo/show_bug.cgi?id=24079
            OSQLParseNode::compress(pNodeTmp);
            pNodeTmp = pNode->getChild(1);
 
            // first extract the inner joins conditions
            GetInnerJoinCriteria(_pView,pNodeTmp);
            // now simplify again, join are checked in ComparisonPredicate
            ::connectivity::OSQLParseNode::absorptions(pNodeTmp);
            pNodeTmp = pNode->getChild(1);
 
            // it could happen that pCondition is not more valid
            eErrorCode = GetORCriteria(_pView,_pSelectionBrw,pNodeTmp, rLevel);
        }
        return eErrorCode;
    }
    SqlParseError GetANDCriteria(   OQueryDesignView* _pView,
                                    OSelectionBrowseBox* _pSelectionBrw,
                                    const  ::connectivity::OSQLParseNode * pCondition,
                                    sal_uInt16& nLevel,
                                    bool bHaving,
                                    bool bAddOrOnOneLine);
    SqlParseError ComparisonPredicate(OQueryDesignView const * _pView,
                            OSelectionBrowseBox* _pSelectionBrw,
                            const ::connectivity::OSQLParseNode * pCondition,
                            const sal_uInt16 nLevel,
                            bool bHaving,
                            bool bAddOrOnOneLine);
    SqlParseError GetORCriteria(OQueryDesignView* _pView,
                                OSelectionBrowseBox* _pSelectionBrw,
                                const ::connectivity::OSQLParseNode * pCondition,
                                sal_uInt16& nLevel ,
                                bool bHaving,
                                bool bAddOrOnOneLine)
    {
        SqlParseError eErrorCode = eOk;
 
        // round brackets around the printout
        if (pCondition->count() == 3 &&
            SQL_ISPUNCTUATION(pCondition->getChild(0),"(") &&
            SQL_ISPUNCTUATION(pCondition->getChild(2),")"))
        {
            eErrorCode = GetORCriteria(_pView,_pSelectionBrw,pCondition->getChild(1),nLevel,bHaving,bAddOrOnOneLine);
        }
        // OR condition
        // a searchcondition can only look like this: search_condition SQL_TOKEN_OR boolean_term
        else if (SQL_ISRULE(pCondition,search_condition))
        {
            for (int i = 0; i < 3 && eErrorCode == eOk ; i+=2)
            {
                const  ::connectivity::OSQLParseNode* pChild = pCondition->getChild(i);
                if ( SQL_ISRULE(pChild,search_condition) )
                    eErrorCode = GetORCriteria(_pView,_pSelectionBrw,pChild,nLevel,bHaving,bAddOrOnOneLine);
                else
                {
                    eErrorCode = GetANDCriteria(_pView,_pSelectionBrw,pChild, nLevel,bHaving, i != 0 && bAddOrOnOneLine);
                    if ( !bAddOrOnOneLine)
                        nLevel++;
                }
            }
        }
        else
            eErrorCode = GetANDCriteria( _pView,_pSelectionBrw,pCondition, nLevel, bHaving,bAddOrOnOneLine );
 
        return eErrorCode;
    }
    bool CheckOrCriteria(const ::connectivity::OSQLParseNode* _pCondition,::connectivity::OSQLParseNode* _pFirstColumnRef)
    {
        bool bRet = true;
        ::connectivity::OSQLParseNode* pFirstColumnRef = _pFirstColumnRef;
        for (size_t i = 0; bRet && i < _pCondition->count(); ++i)
        {
            const  ::connectivity::OSQLParseNode* pChild = _pCondition->getChild(i);
            if ( pChild->isToken() )
                continue;
            else if ( SQL_ISRULE(pChild,search_condition) )
                bRet = CheckOrCriteria(pChild,pFirstColumnRef);
            else
            {
                // this is a simple way to test columns are the same, may be we have to adjust this algo a little bit in future. :-)
                ::connectivity::OSQLParseNode* pSecondColumnRef = pChild->getByRule(::connectivity::OSQLParseNode::column_ref);
                if ( pFirstColumnRef && pSecondColumnRef )
                    bRet = *pFirstColumnRef == *pSecondColumnRef;
                else if ( !pFirstColumnRef )
                    pFirstColumnRef = pSecondColumnRef;
            }
        }
        return bRet;
    }
    SqlParseError GetANDCriteria(   OQueryDesignView* _pView,
                                    OSelectionBrowseBox* _pSelectionBrw,
                                    const  ::connectivity::OSQLParseNode * pCondition,
                                    sal_uInt16& nLevel,
                                    bool bHaving,
                                    bool bAddOrOnOneLine)
    {
        const css::lang::Locale    aLocale = _pView->getLocale();
        const OUString sDecimal = _pView->getDecimalSeparator();
 
        // I will need a cast pointer to my css::sdbcx::Container
        OQueryController& rController = static_cast<OQueryController&>(_pView->getController());
        SqlParseError eErrorCode = eOk;
 
        // round brackets
        if (SQL_ISRULE(pCondition,boolean_primary))
        {
            // check if we have to put the or criteria on one line.
            const  ::connectivity::OSQLParseNode* pSearchCondition = pCondition->getChild(1);
            bool bMustAddOrOnOneLine = CheckOrCriteria(pSearchCondition,nullptr);
            if ( SQL_ISRULE( pSearchCondition, search_condition) ) // we have a or
            {
                _pSelectionBrw->DuplicateConditionLevel( nLevel);
                eErrorCode = GetORCriteria(_pView,_pSelectionBrw,pSearchCondition->getChild(0), nLevel,bHaving,bMustAddOrOnOneLine );
                if ( eErrorCode == eOk )
                {
                    ++nLevel;
                    eErrorCode = GetORCriteria(_pView,_pSelectionBrw,pSearchCondition->getChild(2), nLevel,bHaving,bMustAddOrOnOneLine );
                }
            }
            else
                eErrorCode = GetORCriteria(_pView,_pSelectionBrw,pSearchCondition, nLevel,bHaving,bMustAddOrOnOneLine );
        }
        // The first element is (again) an AND condition
        else if ( SQL_ISRULE(pCondition,boolean_term) )
        {
            OSL_ENSURE(pCondition->count() == 3,"Illegal definition of boolean_term");
            eErrorCode = GetANDCriteria(_pView,_pSelectionBrw,pCondition->getChild(0), nLevel,bHaving,bAddOrOnOneLine );
            if ( eErrorCode == eOk )
                eErrorCode = GetANDCriteria(_pView,_pSelectionBrw,pCondition->getChild(2), nLevel,bHaving,bAddOrOnOneLine );
        }
        else if (SQL_ISRULE( pCondition, comparison_predicate))
        {
            eErrorCode = ComparisonPredicate(_pView,_pSelectionBrw,pCondition,nLevel,bHaving,bAddOrOnOneLine);
        }
        else if( SQL_ISRULE(pCondition,like_predicate) )
        {
            const  ::connectivity::OSQLParseNode* pValueExp = pCondition->getChild(0);
            if (SQL_ISRULE(pValueExp, column_ref ) )
            {
                OUString aCondition;
                Reference< XConnection> xConnection = rController.getConnection();
                if ( xConnection.is() )
                {
                    OUString aColumnName;
                    // the international doesn't matter I have a string
                    pCondition->parseNodeToPredicateStr(aCondition,
                                                        xConnection,
                                                        rController.getNumberFormatter(),
                                                        aLocale,
                                                        sDecimal,
                                                        &rController.getParser().getContext());
 
                    pValueExp->parseNodeToPredicateStr( aColumnName,
                                                        xConnection,
                                                        rController.getNumberFormatter(),
                                                        aLocale,
                                                        sDecimal,
                                                        &rController.getParser().getContext());
 
                    // don't display the column name
                    aCondition = aCondition.copy(aColumnName.getLength());
                    aCondition = aCondition.trim();
                }
 
                OTableFieldDescRef aDragLeft = new OTableFieldDesc();
                if ( eOk == ( eErrorCode = FillDragInfo(_pView,pValueExp,aDragLeft) ))
                {
                    if ( bHaving )
                        aDragLeft->SetGroupBy(true);
                    _pSelectionBrw->AddCondition(aDragLeft, aCondition, nLevel,bAddOrOnOneLine);
                }
            }
            else if(SQL_ISRULEOR3(pValueExp, general_set_fct, set_fct_spec, position_exp)  ||
                      SQL_ISRULEOR3(pValueExp, extract_exp, fold, char_substring_fct)       ||
                      SQL_ISRULEOR2(pValueExp, length_exp, char_value_fct))
            {
                AddFunctionCondition(   _pView,
                                        _pSelectionBrw,
                                        pCondition,
                                        nLevel,
                                        bHaving,
                                        bAddOrOnOneLine);
            }
            else
            {
                eErrorCode = eNoColumnInLike;
                OUString sError(DBA_RES(STR_QRY_LIKE_LEFT_NO_COLUMN));
                _pView->getController().appendError( sError );
            }
        }
        else if(    SQL_ISRULEOR2(pCondition,test_for_null,in_predicate)
                ||  SQL_ISRULEOR2(pCondition,all_or_any_predicate,between_predicate))
        {
            if ( SQL_ISRULEOR2(pCondition->getChild(0), set_fct_spec , general_set_fct ) )
            {
                AddFunctionCondition(   _pView,
                                        _pSelectionBrw,
                                        pCondition,
                                        nLevel,
                                        bHaving,
                                        bAddOrOnOneLine);
            }
            else if ( SQL_ISRULE(pCondition->getChild(0), column_ref ) )
            {
                // parse condition
                OUString sCondition = ParseCondition(rController,pCondition,sDecimal,aLocale,1);
                OTableFieldDescRef  aDragLeft = new OTableFieldDesc();
                if ( eOk == ( eErrorCode = FillDragInfo(_pView,pCondition->getChild(0),aDragLeft)) )
                {
                    if ( bHaving )
                        aDragLeft->SetGroupBy(true);
                    _pSelectionBrw->AddCondition(aDragLeft, sCondition, nLevel,bAddOrOnOneLine);
                }
            }
            else
            {
                // Parse the function condition
                OUString sCondition = ParseCondition(rController,pCondition,sDecimal,aLocale,1);
                Reference< XConnection> xConnection = rController.getConnection();
                    // the international doesn't matter I have a string
                OUString sName;
                pCondition->getChild(0)->parseNodeToPredicateStr(sName,
                                                    xConnection,
                                                    rController.getNumberFormatter(),
                                                    aLocale,
                                                    sDecimal,
                                                    &rController.getParser().getContext());
 
                OTableFieldDescRef aDragLeft = new OTableFieldDesc();
                aDragLeft->SetField(sName);
                aDragLeft->SetFunctionType(FKT_OTHER);
 
                if ( bHaving )
                    aDragLeft->SetGroupBy(true);
                _pSelectionBrw->AddCondition(aDragLeft, sCondition, nLevel,bAddOrOnOneLine);
            }
        }
        else if( SQL_ISRULEOR2(pCondition,existence_test,unique_test) )
        {
            // Parse the function condition
            OUString aCondition = ParseCondition(rController,pCondition,sDecimal,aLocale,0);
 
            OTableFieldDescRef aDragLeft = new OTableFieldDesc();
            aDragLeft->SetField(aCondition);
            aDragLeft->SetFunctionType(FKT_CONDITION);
 
            eErrorCode = _pSelectionBrw->InsertField(aDragLeft,BROWSER_INVALIDID,false).is() ? eOk : eTooManyColumns;
        }
        else //! TODO not supported yet
            eErrorCode = eStatementTooComplex;
        // Pass on the error code
        return eErrorCode;
    }
    SqlParseError AddFunctionCondition(OQueryDesignView const * _pView,
                            OSelectionBrowseBox* _pSelectionBrw,
                            const ::connectivity::OSQLParseNode * pCondition,
                            const sal_uInt16 nLevel,
                            bool bHaving,
                            bool bAddOrOnOneLine)
    {
        SqlParseError eErrorCode = eOk;
        OQueryController& rController = static_cast<OQueryController&>(_pView->getController());
 
        OSQLParseNode* pFunction = pCondition->getChild(0);
 
        OSL_ENSURE(SQL_ISRULEOR3(pFunction, general_set_fct, set_fct_spec, position_exp)  ||
                     SQL_ISRULEOR3(pFunction, extract_exp, fold, char_substring_fct)      ||
                     SQL_ISRULEOR2(pFunction,length_exp,char_value_fct),
                   "Illegal call!");
 
        Reference< XConnection> xConnection = rController.getConnection();
        if(xConnection.is())
        {
            OUString aCondition;
            OUString aColumnName;
            OTableFieldDescRef aDragLeft = new OTableFieldDesc();
            pCondition->parseNodeToPredicateStr(aCondition,
                                                xConnection,
                                                rController.getNumberFormatter(),
                                                _pView->getLocale(),
                                                _pView->getDecimalSeparator(),
                                                &rController.getParser().getContext());
 
            pFunction->parseNodeToStr(  aColumnName,
                                        xConnection,
                                        &rController.getParser().getContext(),
                                        true); // quote is to true because we need quoted elements inside the function
            // don't display the column name
            aCondition = aCondition.copy(aColumnName.getLength());
            aCondition = aCondition.trim();
            if ( aCondition.startsWith("=") ) // ignore the equal sign
                aCondition = aCondition.copy(1);
 
            if ( SQL_ISRULE(pFunction, general_set_fct ) )
            {
                sal_Int32 nFunctionType = FKT_AGGREGATE;
                OSQLParseNode* pParamNode = pFunction->getChild(pFunction->count()-2);
                if ( pParamNode && pParamNode->getTokenValue().toChar() == '*' )
                {
                    OJoinTableView::OTableWindowMap& rTabList = _pView->getTableView()->GetTabWinMap();
                    for (auto const& table : rTabList)
                    {
                        OQueryTableWindow* pTabWin = static_cast<OQueryTableWindow*>(table.second.get());
                        if (pTabWin->ExistsField( u"*"_ustr, aDragLeft ))
                        {
                            aDragLeft->SetAlias(OUString());
                            aDragLeft->SetTable(OUString());
                            break;
                        }
                    }
                }
                else if (pParamNode)
                {
                    eErrorCode = FillDragInfo(_pView,pParamNode,aDragLeft);
                    if ( eOk != eErrorCode && SQL_ISRULE(pParamNode,num_value_exp))
                    {
                        OUString sParameterValue;
                        pParamNode->parseNodeToStr( sParameterValue,
                                                    xConnection,
                                                    &rController.getParser().getContext());
                        nFunctionType |= FKT_NUMERIC;
                        aDragLeft->SetField(sParameterValue);
                        eErrorCode = eOk;
                    }
                }
                aDragLeft->SetFunctionType(nFunctionType);
                if ( bHaving )
                    aDragLeft->SetGroupBy(true);
                aDragLeft->SetFunction(aColumnName.getToken(0, '('));
            }
            else
            {
                // for an unknown function we write the whole text in the field
                aDragLeft->SetField(aColumnName);
                if(bHaving)
                    aDragLeft->SetGroupBy(true);
                aDragLeft->SetFunctionType(FKT_OTHER|FKT_NUMERIC);
            }
            _pSelectionBrw->AddCondition(aDragLeft, aCondition, nLevel,bAddOrOnOneLine);
        }
 
        return eErrorCode;
    }
    SqlParseError ComparisonPredicate(OQueryDesignView const * _pView,
                            OSelectionBrowseBox* _pSelectionBrw,
                            const ::connectivity::OSQLParseNode * pCondition,
                            const sal_uInt16 nLevel,
                            bool bHaving
                            ,bool bAddOrOnOneLine)
    {
        SqlParseError eErrorCode = eOk;
        OQueryController& rController = static_cast<OQueryController&>(_pView->getController());
 
        OSL_ENSURE(SQL_ISRULE( pCondition, comparison_predicate),"ComparisonPredicate: pCondition is not a Comparison Predicate");
        if ( SQL_ISRULE(pCondition->getChild(0), column_ref )
            || SQL_ISRULE(pCondition->getChild(pCondition->count()-1), column_ref) )
        {
            OUString aCondition;
            OTableFieldDescRef aDragLeft = new OTableFieldDesc();
 
            if ( SQL_ISRULE(pCondition->getChild(0), column_ref ) && SQL_ISRULE(pCondition->getChild(pCondition->count()-1), column_ref ) )
            {
                OTableFieldDescRef aDragRight = new OTableFieldDesc();
                eErrorCode = FillDragInfo(_pView,pCondition->getChild(0),aDragLeft);
                if (eOk != eErrorCode)
                    return eErrorCode;
                eErrorCode = FillDragInfo(_pView,pCondition->getChild(2),aDragRight);
                if (eOk != eErrorCode)
                    return eErrorCode;
 
                OQueryTableConnection* pConn = static_cast<OQueryTableConnection*>(
                                                    _pView->getTableView()->GetTabConn(static_cast<OQueryTableWindow*>(aDragLeft->GetTabWindow()),
                                                                                       static_cast<OQueryTableWindow*>(aDragRight->GetTabWindow()),
                                                                                       true));
                if ( pConn )
                {
                    OConnectionLineDataVec& rLineDataList = pConn->GetData()->GetConnLineDataList();
                    for (auto const& lineData : rLineDataList)
                    {
                        if(lineData->GetSourceFieldName() == aDragLeft->GetField() ||
                           lineData->GetDestFieldName() == aDragLeft->GetField() )
                            return eOk;
                    }
                }
            }
 
            sal_uInt32 nPos = 0;
            if(SQL_ISRULE(pCondition->getChild(0), column_ref ))
            {
                nPos = 0;
                sal_uInt32 i=1;
 
                // don't display the equal
                if (pCondition->getChild(i)->getNodeType() == SQLNodeType::Equal)
                    i++;
 
                // parse the condition
                aCondition = ParseCondition(rController
                                            ,pCondition
                                            ,_pView->getDecimalSeparator()
                                            ,_pView->getLocale()
                                            ,i);
            }
            else if( SQL_ISRULE(pCondition->getChild(pCondition->count()-1), column_ref ) )
            {
                nPos = pCondition->count()-1;
 
                sal_Int32 i = static_cast<sal_Int32>(pCondition->count() - 2);
                switch (pCondition->getChild(i)->getNodeType())
                {
                    case SQLNodeType::Equal:
                        // don't display the equal
                        i--;
                        break;
                    case SQLNodeType::Less:
                        // take the opposite as we change the order
                        i--;
                        aCondition += ">";
                        break;
                    case SQLNodeType::LessEq:
                        // take the opposite as we change the order
                        i--;
                        aCondition += ">=";
                        break;
                    case SQLNodeType::Great:
                        // take the opposite as we change the order
                        i--;
                        aCondition += "<";
                        break;
                    case SQLNodeType::GreatEq:
                        // take the opposite as we change the order
                        i--;
                        aCondition += "<=";
                        break;
                    default:
                        break;
                }
 
                // go backward
                Reference< XConnection> xConnection = rController.getConnection();
                if(xConnection.is())
                {
                    for (; i >= 0; i--)
                        pCondition->getChild(i)->parseNodeToPredicateStr(aCondition,
                                                xConnection,
                                                rController.getNumberFormatter(),
                                                _pView->getLocale(),
                                                _pView->getDecimalSeparator(),
                                                &rController.getParser().getContext());
                }
            }
            // else ???
 
            if( eOk == ( eErrorCode = FillDragInfo(_pView,pCondition->getChild(nPos),aDragLeft)))
            {
                if(bHaving)
                    aDragLeft->SetGroupBy(true);
                _pSelectionBrw->AddCondition(aDragLeft, aCondition, nLevel,bAddOrOnOneLine);
            }
        }
        else if( SQL_ISRULEOR2(pCondition->getChild(0), set_fct_spec , general_set_fct ) )
        {
            AddFunctionCondition(   _pView,
                                    _pSelectionBrw,
                                    pCondition,
                                    nLevel,
                                    bHaving,
                                    bAddOrOnOneLine);
        }
        else // it can only be an Expr
        {
            OUString aName,aCondition;
 
            // Field name
            Reference< XConnection> xConnection = rController.getConnection();
            if(xConnection.is())
            {
                ::connectivity::OSQLParseNode *pLhs = pCondition->getChild(0);
                ::connectivity::OSQLParseNode *pRhs = pCondition->getChild(2);
                pLhs->parseNodeToStr(aName,
                                     xConnection,
                                     &rController.getParser().getContext(),
                                     true);
                // Criteria
                aCondition = pCondition->getChild(1)->getTokenValue();
                pRhs->parseNodeToPredicateStr(aCondition,
                                                            xConnection,
                                                            rController.getNumberFormatter(),
                                                            _pView->getLocale(),
                                                            _pView->getDecimalSeparator(),
                                                            &rController.getParser().getContext());
            }
 
            OTableFieldDescRef aDragLeft = new OTableFieldDesc();
            aDragLeft->SetField(aName);
            aDragLeft->SetFunctionType(FKT_OTHER|FKT_NUMERIC);
            // and add it on
            _pSelectionBrw->AddCondition(aDragLeft, aCondition, nLevel,bAddOrOnOneLine);
        }
        return eErrorCode;
    }
 
    OQueryTableWindow* lcl_findColumnInTables( const OUString& _rColumName, const OJoinTableView::OTableWindowMap& _rTabList, OTableFieldDescRef const & _rInfo )
    {
        for (auto const& table : _rTabList)
        {
            OQueryTableWindow* pTabWin = static_cast< OQueryTableWindow* >( table.second.get() );
            if ( pTabWin && pTabWin->ExistsField( _rColumName, _rInfo ) )
                return pTabWin;
        }
        return nullptr;
    }
 
    void InsertColumnRef(const OQueryDesignView* _pView,
                        const ::connectivity::OSQLParseNode * pColumnRef,
                        OUString& aColumnName,
                        const OUString& aColumnAlias,
                        OUString& aTableRange,
                        OTableFieldDescRef const & _raInfo,
                        OJoinTableView::OTableWindowMap const * pTabList)
    {
 
        // Put the table names together
        ::connectivity::OSQLParseTreeIterator& rParseIter = static_cast<OQueryController&>(_pView->getController()).getParseIterator();
        rParseIter.getColumnRange( pColumnRef, aColumnName, aTableRange );
 
        bool bFound(false);
        OSL_ENSURE(!aColumnName.isEmpty(),"Column name must not be empty");
        if (aTableRange.isEmpty())
        {
            // SELECT column, ...
            bFound = nullptr != lcl_findColumnInTables( aColumnName, *pTabList, _raInfo );
            if ( bFound && ( aColumnName.toChar() != '*' ) )
                _raInfo->SetFieldAlias(aColumnAlias);
        }
        else
        {
            // SELECT range.column, ...
            OQueryTableWindow* pTabWin = static_cast<OQueryTableView*>(_pView->getTableView())->FindTable(aTableRange);
 
            if (pTabWin && pTabWin->ExistsField(aColumnName, _raInfo))
            {
                if(aColumnName.toChar() != '*')
                    _raInfo->SetFieldAlias(aColumnAlias);
                bFound = true;
            }
        }
        if (!bFound)
        {
            _raInfo->SetTable(OUString());
            _raInfo->SetAlias(OUString());
            _raInfo->SetField(aColumnName);
            _raInfo->SetFieldAlias(aColumnAlias);   // nyi : here it continues Expr_1, Expr_2 ...
            _raInfo->SetFunctionType(FKT_OTHER);
        }
    }
    bool checkJoinConditions(   const OQueryDesignView* _pView,
                                    const ::connectivity::OSQLParseNode* _pNode )
    {
        const ::connectivity::OSQLParseNode* pJoinNode = nullptr;
        bool bRet = true;
        if (SQL_ISRULE(_pNode,qualified_join))
            pJoinNode = _pNode;
        else if (SQL_ISRULE(_pNode,table_ref)
                &&  _pNode->count() == 3
                &&  SQL_ISPUNCTUATION(_pNode->getChild(0),"(")
                &&  SQL_ISPUNCTUATION(_pNode->getChild(2),")") ) // '(' joined_table ')'
            pJoinNode = _pNode->getChild(1);
        else if (! ( SQL_ISRULE(_pNode, table_ref) && _pNode->count() == 2) ) // table_node table_primary_as_range_column
            bRet = false;
 
        if (pJoinNode && !InsertJoin(_pView,pJoinNode))
            bRet = false;
        return bRet;
    }
    bool InsertJoin(const OQueryDesignView* _pView,
                        const ::connectivity::OSQLParseNode *pNode)
    {
        OSL_ENSURE( SQL_ISRULE( pNode, qualified_join ) || SQL_ISRULE( pNode, joined_table ) || SQL_ISRULE( pNode, cross_union ),
            "OQueryDesignView::InsertJoin: Error in the Parse Tree");
 
        if (SQL_ISRULE(pNode,joined_table))
            return InsertJoin(_pView,pNode->getChild(1));
 
        // first check the left and right side
        const ::connectivity::OSQLParseNode* pRightTableRef = pNode->getChild(3); // table_ref
        if ( SQL_ISRULE(pNode, qualified_join) && SQL_ISTOKEN(pNode->getChild(1),NATURAL) )
            pRightTableRef = pNode->getChild(4); // table_ref
 
        if ( !checkJoinConditions(_pView,pNode->getChild(0)) || !checkJoinConditions(_pView,pRightTableRef))
            return false;
 
        // named column join may be implemented later
        // SQL_ISRULE(pNode->getChild(4),named_columns_join)
        EJoinType eJoinType = INNER_JOIN;
        bool bNatural = false;
        if ( SQL_ISRULE(pNode, qualified_join) )
        {
            ::connectivity::OSQLParseNode* pJoinType = pNode->getChild(1); // join_type
            if ( SQL_ISTOKEN(pJoinType,NATURAL) )
            {
                bNatural = true;
                pJoinType = pNode->getChild(2);
            }
 
            if (SQL_ISRULE(pJoinType,join_type) && (!pJoinType->count() || SQL_ISTOKEN(pJoinType->getChild(0),INNER)))
            {
                eJoinType = INNER_JOIN;
            }
            else
            {
                if (SQL_ISRULE(pJoinType,join_type))       // one level deeper
                    pJoinType = pJoinType->getChild(0);
 
                if (SQL_ISTOKEN(pJoinType->getChild(0),LEFT))
                    eJoinType = LEFT_JOIN;
                else if(SQL_ISTOKEN(pJoinType->getChild(0),RIGHT))
                    eJoinType = RIGHT_JOIN;
                else
                    eJoinType = FULL_JOIN;
            }
            if ( SQL_ISRULE(pNode->getChild(4),join_condition) )
            {
                if ( InsertJoinConnection(_pView,pNode->getChild(4)->getChild(1), eJoinType,pNode->getChild(0),pRightTableRef) != eOk )
                    return false;
            }
        }
        else if ( SQL_ISRULE(pNode, cross_union) )
        {
            eJoinType = CROSS_JOIN;
            pRightTableRef = pNode->getChild(pNode->count() - 1);
        }
        else
            return false;
 
        if ( eJoinType != CROSS_JOIN && !bNatural )
            return true;
 
        OQueryTableWindow*  pLeftWindow = static_cast<OQueryTableView*>(_pView->getTableView())->FindTable( getTableRange(_pView,pNode->getChild(0)) );
        OQueryTableWindow*  pRightWindow = static_cast<OQueryTableView*>(_pView->getTableView())->FindTable( getTableRange(_pView,pRightTableRef) );
        OSL_ENSURE(pLeftWindow && pRightWindow,"Table Windows could not be found!");
        if ( !pLeftWindow || !pRightWindow )
            return false;
 
        OTableFieldDescRef aDragLeft  = new OTableFieldDesc();
        aDragLeft->SetTabWindow(pLeftWindow);
        aDragLeft->SetTable(pLeftWindow->GetTableName());
        aDragLeft->SetAlias(pLeftWindow->GetAliasName());
 
        OTableFieldDescRef aDragRight = new OTableFieldDesc();
        aDragRight->SetTabWindow(pRightWindow);
        aDragRight->SetTable(pRightWindow->GetTableName());
        aDragRight->SetAlias(pRightWindow->GetAliasName());
 
        insertConnection(_pView,eJoinType,aDragLeft,aDragRight,bNatural);
 
        return true;
    }
    void insertUnUsedFields(OQueryDesignView const * _pView,OSelectionBrowseBox* _pSelectionBrw)
    {
        // now we have to insert the fields which aren't in the statement
        OQueryController& rController = static_cast<OQueryController&>(_pView->getController());
        OTableFields& rUnUsedFields = rController.getUnUsedFields();
        for (auto & unusedField : rUnUsedFields)
            if(_pSelectionBrw->InsertField(unusedField,BROWSER_INVALIDID,false,false).is())
                unusedField = nullptr;
        OTableFields().swap( rUnUsedFields );
    }
 
    SqlParseError InitFromParseNodeImpl(OQueryDesignView* _pView,OSelectionBrowseBox* _pSelectionBrw)
    {
        SqlParseError eErrorCode = eOk;
 
        OQueryController& rController = static_cast<OQueryController&>(_pView->getController());
 
        _pSelectionBrw->PreFill();
        _pSelectionBrw->SetReadOnly(rController.isReadOnly());
        _pSelectionBrw->Fill();
 
        ::connectivity::OSQLParseTreeIterator& aIterator = rController.getParseIterator();
        const ::connectivity::OSQLParseNode* pParseTree = aIterator.getParseTree();
 
        do
        {
            if ( !pParseTree )
            {
                // now we have to insert the fields which aren't in the statement
                insertUnUsedFields(_pView,_pSelectionBrw);
                break;
            }
 
            if ( !rController.isEscapeProcessing() ) // not allowed in this mode
            {
                eErrorCode = eNativeMode;
                break;
            }
 
            if ( !( SQL_ISRULE( pParseTree, select_statement ) ) )
            {
                eErrorCode = eNoSelectStatement;
                break;
            }
 
            const OSQLParseNode* pTableExp = pParseTree->getChild(3);
            if ( pTableExp->getChild(7)->count() > 0 || pTableExp->getChild(8)->count() > 0)
            {
                eErrorCode = eStatementTooComplex;
                break;
            }
 
            Reference< XConnection> xConnection = rController.getConnection();
            if ( !xConnection.is() )
            {
                OSL_FAIL( "InitFromParseNodeImpl: no connection? no connection!" );
                break;
            }
 
            const OSQLTables& aMap = aIterator.getTables();
            ::comphelper::UStringMixLess aTmp(aMap.key_comp());
            ::comphelper::UStringMixEqual aKeyComp( aTmp.isCaseSensitive() );
 
            Reference< XDatabaseMetaData >  xMetaData = xConnection->getMetaData();
            try
            {
                sal_Int32 nMax = xMetaData->getMaxTablesInSelect();
                if ( nMax && nMax < static_cast<sal_Int32>(aMap.size()) )
                {
                    eErrorCode = eTooManyTables;
                    break;
                }
 
                OUString sComposedName;
                OUString sAlias;
 
                OQueryTableView* pTableView = static_cast<OQueryTableView*>(_pView->getTableView());
                pTableView->clearLayoutInformation();
                for (auto const& elem : aMap)
                {
                    OSQLTable xTable = elem.second;
                    Reference< XPropertySet > xTableProps( xTable, UNO_QUERY_THROW );
 
                    sAlias = elem.first;
 
                    // check whether this is a query
                    Reference< XPropertySetInfo > xPSI = xTableProps->getPropertySetInfo();
                    bool bIsQuery = xPSI.is() && xPSI->hasPropertyByName( PROPERTY_COMMAND );
 
                    if ( bIsQuery )
                        OSL_VERIFY( xTableProps->getPropertyValue( PROPERTY_NAME ) >>= sComposedName );
                    else
                    {
                        sComposedName = ::dbtools::composeTableName( xMetaData, xTableProps, ::dbtools::EComposeRule::InDataManipulation, false );
 
                        // if the alias is the complete (composed) table, then shorten it
                        if ( aKeyComp( sComposedName, elem.first ) )
                        {
                            OUString sCatalog, sSchema, sTable;
                            ::dbtools::qualifiedNameComponents( xMetaData, sComposedName, sCatalog, sSchema, sTable, ::dbtools::EComposeRule::InDataManipulation );
                            sAlias = sTable;
                        }
                    }
 
                    // find the existent window for this alias
                    OQueryTableWindow* pExistentWin = pTableView->FindTable( sAlias );
                    if ( !pExistentWin )
                    {
                        pTableView->AddTabWin( sComposedName, sAlias );  // don't create data here
                    }
                    else
                    {
                        // there already exists a window for this alias...
                        if ( !aKeyComp( pExistentWin->GetData()->GetComposedName(), sComposedName ) )
                            // ... but for another complete table name -> new window
                            pTableView->AddTabWin(sComposedName, sAlias);
                    }
                }
 
                // now delete the data for which we haven't any tablewindow
                OJoinTableView::OTableWindowMap aTableMap(pTableView->GetTabWinMap());
                for (auto const& table : aTableMap)
                {
                    if(aMap.find(table.second->GetComposedName())  == aMap.end() &&
                        aMap.find(table.first)                     == aMap.end())
                        pTableView->RemoveTabWin(table.second);
                }
 
                if ( eOk == (eErrorCode = FillOuterJoins(_pView,pTableExp->getChild(0)->getChild(1))) )
                {
                    // check if we have a distinct statement
                    if(SQL_ISTOKEN(pParseTree->getChild(1),DISTINCT))
                    {
                        rController.setDistinct(true);
                        rController.InvalidateFeature(SID_QUERY_DISTINCT_VALUES);
                    }
                    else
                    {
                        rController.setDistinct(false);
                    }
 
                    ///check if query has a limit
                    if( pTableExp->getChild(6)->count() >= 2 && pTableExp->getChild(6)->getChild(1) )
                    {
                        rController.setLimit(
                            pTableExp->getChild(6)->getChild(1)->getTokenValue().toInt64() );
                    }
                    else
                    {
                        rController.setLimit(-1);
                    }
 
                    if ( (eErrorCode = InstallFields(_pView, pParseTree, &pTableView->GetTabWinMap())) == eOk )
                    {
                        // GetSelectionCriteria must be called before GetHavingCriteria
                        sal_uInt16 nLevel=0;
 
                        if ( eOk == (eErrorCode = GetSelectionCriteria(_pView,_pSelectionBrw,pParseTree,nLevel)) )
                        {
                            if ( eOk == (eErrorCode = GetGroupCriteria(_pView,_pSelectionBrw,pParseTree)) )
                            {
                                if ( eOk == (eErrorCode = GetHavingCriteria(_pView,_pSelectionBrw,pParseTree,nLevel)) )
                                {
                                    if ( eOk == (eErrorCode = GetOrderCriteria(_pView,_pSelectionBrw,pParseTree)) )
                                        insertUnUsedFields(_pView,_pSelectionBrw);
                                }
                            }
                        }
                    }
                }
            }
            catch(SQLException&)
            {
                OSL_FAIL("getMaxTablesInSelect!");
            }
        }
        while ( false );
 
        // New Undo-Actions were created in the Manager by the regeneration
        rController.ClearUndoManager();
        _pSelectionBrw->Invalidate();
        return eErrorCode;
    }
    /** fillSelectSubList
        @return
            <TRUE/> when columns could be inserted otherwise <FALSE/>
    */
    SqlParseError fillSelectSubList(    OQueryDesignView* _pView,
                                OJoinTableView::OTableWindowMap* _pTabList)
    {
        SqlParseError eErrorCode = eOk;
        bool bFirstField = true;
        for (auto const& table : *_pTabList)
        {
            OQueryTableWindow* pTabWin = static_cast<OQueryTableWindow*>(table.second.get());
            OTableFieldDescRef  aInfo = new OTableFieldDesc();
            if (pTabWin->ExistsField( u"*"_ustr, aInfo ))
            {
                eErrorCode = _pView->InsertField(aInfo, bFirstField);
                bFirstField = false;
                if (eErrorCode != eOk)
                    break;
            }
        }
        return eErrorCode;
    }
    SqlParseError InstallFields(OQueryDesignView* _pView,
                                const ::connectivity::OSQLParseNode* pNode,
                                OJoinTableView::OTableWindowMap* pTabList )
    {
        if( pNode==nullptr || !SQL_ISRULE(pNode,select_statement))
            return eNoSelectStatement;
 
        ::connectivity::OSQLParseNode* pParseTree = pNode->getChild(2); // selection
        bool bFirstField = true;    // When initializing, the first field must be reactivated
 
        SqlParseError eErrorCode = eOk;
 
        if ( pParseTree->isRule() && SQL_ISPUNCTUATION(pParseTree->getChild(0),"*") )
        {
            // SELECT * ...
            eErrorCode = fillSelectSubList(_pView,pTabList);
        }
        else if (SQL_ISRULE(pParseTree,scalar_exp_commalist) )
        {
            // SELECT column, ...
            OQueryController& rController = static_cast<OQueryController&>(_pView->getController());
            Reference< XConnection> xConnection = rController.getConnection();
 
            OUString aColumnName,aTableRange;
            for (size_t i = 0; i < pParseTree->count() && eOk == eErrorCode ; ++i)
            {
                ::connectivity::OSQLParseNode * pColumnRef = pParseTree->getChild(i);
 
                do {
 
                if ( SQL_ISRULE(pColumnRef,select_sublist) )
                {
                     eErrorCode = fillSelectSubList(_pView,pTabList);
                     break;
                }
 
                if ( SQL_ISRULE(pColumnRef,derived_column) )
                {
                    OUString aColumnAlias(connectivity::OSQLParseTreeIterator::getColumnAlias(pColumnRef)); // might be empty
                    pColumnRef = pColumnRef->getChild(0);
                    OTableFieldDescRef aInfo = new OTableFieldDesc();
 
                    if (    pColumnRef->getKnownRuleID() != OSQLParseNode::subquery &&
                            pColumnRef->count() == 3 &&
                            SQL_ISPUNCTUATION(pColumnRef->getChild(0),"(") &&
                            SQL_ISPUNCTUATION(pColumnRef->getChild(2),")")
                        )
                        pColumnRef = pColumnRef->getChild(1);
 
                    if (SQL_ISRULE(pColumnRef,column_ref))
                    {
                        InsertColumnRef(_pView,pColumnRef,aColumnName,aColumnAlias,aTableRange,aInfo,pTabList);
                        eErrorCode = _pView->InsertField(aInfo, bFirstField);
                        bFirstField = false;
                    }
                    else if(SQL_ISRULEOR3(pColumnRef, general_set_fct, set_fct_spec, position_exp)  ||
                              SQL_ISRULEOR3(pColumnRef, extract_exp, fold, char_substring_fct)      ||
                              SQL_ISRULEOR2(pColumnRef,length_exp,char_value_fct))
                    {
                        OUString aColumns;
                        pColumnRef->parseNodeToPredicateStr(aColumns,
                                                            xConnection,
                                                            rController.getNumberFormatter(),
                                                            _pView->getLocale(),
                                                            _pView->getDecimalSeparator(),
                                                            &rController.getParser().getContext());
 
                        sal_Int32 nFunctionType = FKT_NONE;
                        ::connectivity::OSQLParseNode* pParamRef = nullptr;
                        sal_Int32 nColumnRefPos = pColumnRef->count() - 2;
                        if ( nColumnRefPos >= 0 && o3tl::make_unsigned(nColumnRefPos) < pColumnRef->count() )
                            pParamRef = pColumnRef->getChild(nColumnRefPos);
 
                        if ( SQL_ISRULE(pColumnRef,general_set_fct)
                            && pParamRef &&  SQL_ISRULE(pParamRef,column_ref) )
                        {
                            // Check the parameters for Column references
                            InsertColumnRef(_pView,pParamRef,aColumnName,aColumnAlias,aTableRange,aInfo,pTabList);
                        }
                        else if ( SQL_ISRULE(pColumnRef,general_set_fct) )
                        {
                            if ( pParamRef && pParamRef->getTokenValue().toChar() == '*' )
                            {
                                for (auto const& table : *pTabList)
                                {
                                    OQueryTableWindow* pTabWin = static_cast<OQueryTableWindow*>(table.second.get());
                                    if (pTabWin->ExistsField( u"*"_ustr, aInfo ))
                                    {
                                        aInfo->SetAlias(OUString());
                                        aInfo->SetTable(OUString());
                                        break;
                                    }
                                }
                            }
                            else
                            {
                                OUString sFieldName = aColumns;
                                if ( pParamRef )
                                {   // we got an aggregate function but without column name inside
                                    // so we set the whole argument of the function as field name
                                    nFunctionType |= FKT_NUMERIC;
                                    sFieldName.clear();
                                    pParamRef->parseNodeToStr(  sFieldName,
                                                        xConnection,
                                                        &rController.getParser().getContext(),
                                                        true); // quote is to true because we need quoted elements inside the function
                                }
                                aInfo->SetDataType(DataType::DOUBLE);
                                aInfo->SetFieldType(TAB_NORMAL_FIELD);
                                aInfo->SetField(sFieldName);
                            }
                            aInfo->SetTabWindow(nullptr);
                            aInfo->SetFieldAlias(aColumnAlias);
                        }
                        else
                        {
                            _pView->fillFunctionInfo(pColumnRef,aColumns,aInfo);
                            aInfo->SetFieldAlias(aColumnAlias);
                        }
 
                        if ( SQL_ISRULE(pColumnRef,general_set_fct) )
                        {
                            aInfo->SetFunctionType(nFunctionType|FKT_AGGREGATE);
                            aInfo->SetFunction(OUString(comphelper::string::stripEnd(o3tl::getToken(aColumns,0,'('), ' ')));
                        }
                        else
                            aInfo->SetFunctionType(nFunctionType|FKT_OTHER);
 
                        eErrorCode = _pView->InsertField(aInfo, bFirstField);
                        bFirstField = false;
                    }
                    else
                    {
                        OUString aColumns;
                        pColumnRef->parseNodeToStr( aColumns,
                                                    xConnection,
                                                    &rController.getParser().getContext(),
                                                    true); // quote is to true because we need quoted elements inside the function
 
                        aInfo->SetTabWindow( nullptr );
 
                        // since we support queries in queries, the thingie might belong to an existing "table"
                        OQueryTableWindow* pExistingTable = lcl_findColumnInTables( aColumns, *pTabList, aInfo );
                        if ( pExistingTable )
                        {
                            aInfo->SetTabWindow( pExistingTable );
                            aInfo->SetTable( pExistingTable->GetTableName() );
                            aInfo->SetAlias( pExistingTable->GetAliasName() );
                        }
 
                        aInfo->SetDataType(DataType::DOUBLE);
                        aInfo->SetFieldType(TAB_NORMAL_FIELD);
                        aInfo->SetField(aColumns);
                        aInfo->SetFieldAlias(aColumnAlias);
                        aInfo->SetFunctionType(FKT_NUMERIC | FKT_OTHER);
 
                        eErrorCode = _pView->InsertField(aInfo, bFirstField);
                        bFirstField = false;
                    }
 
                    break;
                }
 
                OSL_FAIL( "InstallFields: don't know how to interpret this parse node!" );
 
                } while ( false );
            }
        }
        else
            eErrorCode = eStatementTooComplex;
 
        return eErrorCode;
    }
    SqlParseError GetOrderCriteria( OQueryDesignView* _pView,
                            OSelectionBrowseBox* _pSelectionBrw,
                            const ::connectivity::OSQLParseNode* pParseRoot )
    {
        SqlParseError eErrorCode = eOk;
        if (!pParseRoot->getChild(3)->getChild(ORDER_BY_CHILD_POS)->isLeaf())
        {
            ::connectivity::OSQLParseNode* pNode = pParseRoot->getChild(3)->getChild(ORDER_BY_CHILD_POS)->getChild(2);
            ::connectivity::OSQLParseNode* pParamRef = nullptr;
 
            OQueryController& rController = static_cast<OQueryController&>(_pView->getController());
            EOrderDir eOrderDir;
            for( size_t i=0 ; i<pNode->count() ; i++ )
            {
                OTableFieldDescRef aDragLeft = new OTableFieldDesc();
                eOrderDir = ORDER_ASC;
                ::connectivity::OSQLParseNode*  pChild = pNode->getChild( i );
 
                if (SQL_ISTOKEN( pChild->getChild(1), DESC ) )
                    eOrderDir = ORDER_DESC;
 
                ::connectivity::OSQLParseNode* pArgument = pChild->getChild(0);
 
                if(SQL_ISRULE(pArgument,column_ref))
                {
                    if( eOk == FillDragInfo(_pView,pArgument,aDragLeft))
                        _pSelectionBrw->AddOrder( aDragLeft, eOrderDir, i);
                    else // it could be an alias name for a field
                    {
                        OUString aTableRange,aColumnName;
                        ::connectivity::OSQLParseTreeIterator& rParseIter = rController.getParseIterator();
                        rParseIter.getColumnRange( pArgument, aColumnName, aTableRange );
 
                        OTableFields& aList = rController.getTableFieldDesc();
                        for (auto const& elem : aList)
                        {
                            if(elem.is() && elem->GetFieldAlias() == aColumnName)
                                elem->SetOrderDir( eOrderDir );
                        }
                    }
                }
                else if(SQL_ISRULE(pArgument, general_set_fct ) &&
                        SQL_ISRULE(pParamRef = pArgument->getChild(pArgument->count()-2),column_ref) &&
                        eOk == FillDragInfo(_pView,pParamRef,aDragLeft))
                    _pSelectionBrw->AddOrder( aDragLeft, eOrderDir, i );
                else if( SQL_ISRULE(pArgument, set_fct_spec ) )
                {
 
                    Reference< XConnection> xConnection = rController.getConnection();
                    if(xConnection.is())
                    {
                        OUString sCondition;
                        pArgument->parseNodeToPredicateStr(sCondition,
                                                            xConnection,
                                                            rController.getNumberFormatter(),
                                                            _pView->getLocale(),
                                                            _pView->getDecimalSeparator(),
                                                            &rController.getParser().getContext());
                        _pView->fillFunctionInfo(pArgument,sCondition,aDragLeft);
                        aDragLeft->SetFunctionType(FKT_OTHER);
                        aDragLeft->SetOrderDir(eOrderDir);
                        aDragLeft->SetVisible(false);
                        _pSelectionBrw->AddOrder( aDragLeft, eOrderDir, i );
                    }
                    else
                        eErrorCode = eColumnNotFound;
                }
                else
                    eErrorCode = eColumnNotFound;
            }
        }
        return eErrorCode;
    }
    SqlParseError GetHavingCriteria(    OQueryDesignView* _pView,
                            OSelectionBrowseBox* _pSelectionBrw,
                            const ::connectivity::OSQLParseNode* pSelectRoot,
                            sal_uInt16& rLevel )
    {
        SqlParseError eErrorCode = eOk;
        if (!pSelectRoot->getChild(3)->getChild(3)->isLeaf())
            eErrorCode = GetORCriteria(_pView,_pSelectionBrw,pSelectRoot->getChild(3)->getChild(3)->getChild(1),rLevel, true);
        return eErrorCode;
    }
    SqlParseError GetGroupCriteria( OQueryDesignView* _pView,
                            OSelectionBrowseBox* _pSelectionBrw,
                            const ::connectivity::OSQLParseNode* pSelectRoot )
    {
        SqlParseError eErrorCode = eOk;
        if (!pSelectRoot->getChild(3)->getChild(2)->isLeaf()) // opt_group_by_clause
        {
            OQueryController& rController = static_cast<OQueryController&>(_pView->getController());
            ::connectivity::OSQLParseNode* pGroupBy = pSelectRoot->getChild(3)->getChild(2)->getChild(2);
 
            for( size_t i=0 ; i < pGroupBy->count() && eOk == eErrorCode; ++i )
            {
                OTableFieldDescRef aDragInfo = new OTableFieldDesc();
                ::connectivity::OSQLParseNode* pParamRef = nullptr;
                ::connectivity::OSQLParseNode* pArgument = pGroupBy->getChild( i );
                if(SQL_ISRULE(pArgument,column_ref))
                {
                    if ( eOk == (eErrorCode = FillDragInfo(_pView,pArgument,aDragInfo)) )
                    {
                        aDragInfo->SetGroupBy(true);
                        _pSelectionBrw->AddGroupBy(aDragInfo);
                    }
                }
                else if(SQL_ISRULE(pArgument, general_set_fct ) &&
                        SQL_ISRULE(pParamRef = pArgument->getChild(pArgument->count()-2),column_ref) &&
                        eOk == FillDragInfo(_pView,pParamRef,aDragInfo))
                {
                    aDragInfo->SetGroupBy(true);
                    _pSelectionBrw->AddGroupBy( aDragInfo );
                }
                else if( SQL_ISRULE(pArgument, set_fct_spec ) )
                {
                    Reference< XConnection> xConnection = rController.getConnection();
                    if(xConnection.is())
                    {
                        OUString sGroupByExpression;
                        pArgument->parseNodeToStr(  sGroupByExpression,
                                                    xConnection,
                                                    &rController.getParser().getContext(),
                                                    true); // quote is to true because we need quoted elements inside the function
                        _pView->fillFunctionInfo(pArgument,sGroupByExpression,aDragInfo);
                        aDragInfo->SetFunctionType(FKT_OTHER);
                        aDragInfo->SetGroupBy(true);
                        aDragInfo->SetVisible(false);
                        _pSelectionBrw->AddGroupBy( aDragInfo );
                    }
                    else
                        eErrorCode = eColumnNotFound;
                }
            }
        }
        return eErrorCode;
    }
 
    OUString getParseErrorMessage( SqlParseError _eErrorCode )
    {
        TranslateId pResId;
        switch (_eErrorCode)
        {
            case eIllegalJoin:
                pResId = STR_QRY_ILLEGAL_JOIN;
                break;
            case eStatementTooLong:
                pResId = STR_QRY_TOO_LONG_STATEMENT;
                break;
            case eNoConnection:
                pResId = STR_QRY_SYNTAX;
                break;
            case eNoSelectStatement:
                pResId = STR_QRY_NOSELECT;
                break;
            case eNoColumnInLike:
                pResId = STR_QRY_SYNTAX;
                break;
            case eColumnNotFound:
                pResId = STR_QRY_SYNTAX;
                break;
            case eNativeMode:
                pResId = STR_QRY_NATIVE;
                break;
            case eTooManyTables:
                pResId = STR_QRY_TOO_MANY_TABLES;
                break;
            case eTooManyColumns:
                pResId = STR_QRY_TOO_MANY_COLUMNS;
                break;
            case eStatementTooComplex:
                pResId = STR_QRY_TOOCOMPLEX;
                break;
            default:
                pResId = STR_QRY_SYNTAX;
                break;
        }
        return DBA_RES(pResId);
    }
}
 
// end of anonymous namespace
 
OQueryDesignView::OQueryDesignView( OQueryContainerWindow* _pParent,
                                    OQueryController& _rController,
                                    const Reference< XComponentContext >& _rxContext)
    :OJoinDesignView( _pParent, _rController, _rxContext )
    ,m_aSplitter( VclPtr<Splitter>::Create(this) )
    ,m_eChildFocus(NONE)
    ,m_bInSplitHandler( false )
{
 
    try
    {
        SvtSysLocale aSysLocale;
        m_aLocale = aSysLocale.GetLanguageTag().getLocale();
        m_sDecimalSep = aSysLocale.GetLocaleData().getNumDecimalSep();
    }
    catch(Exception&)
    {
    }
 
    m_pSelectionBox = VclPtr<OSelectionBrowseBox>::Create(this);
 
    setNoneVisibleRow(static_cast<OQueryController&>(getController()).getVisibleRows());
    m_pSelectionBox->Show();
    // setup Splitter
    m_aSplitter->SetSplitHdl(LINK(this, OQueryDesignView,SplitHdl));
    m_aSplitter->Show();
 
}
 
OQueryDesignView::~OQueryDesignView()
{
    disposeOnce();
}
 
void OQueryDesignView::dispose()
{
    if ( m_pTableView )
        ::dbaui::notifySystemWindow(this,m_pTableView,::comphelper::mem_fun(&TaskPaneList::RemoveWindow));
    m_pSelectionBox.disposeAndClear();
    m_aSplitter.disposeAndClear();
    OJoinDesignView::dispose();
}
 
IMPL_LINK_NOARG( OQueryDesignView, SplitHdl, Splitter*, void )
{
    if (!getController().isReadOnly())
    {
        m_bInSplitHandler = true;
        m_aSplitter->SetPosPixel( Point( m_aSplitter->GetPosPixel().X(),m_aSplitter->GetSplitPosPixel() ) );
        static_cast<OQueryController&>(getController()).setSplitPos(m_aSplitter->GetSplitPosPixel());
        static_cast<OQueryController&>(getController()).setModified( true );
        Resize();
        m_bInSplitHandler = true;
    }
}
 
void OQueryDesignView::Construct()
{
    m_pTableView = VclPtr<OQueryTableView>::Create(m_pScrollWindow,this);
    ::dbaui::notifySystemWindow(this,m_pTableView,::comphelper::mem_fun(&TaskPaneList::AddWindow));
    OJoinDesignView::Construct();
}
 
void OQueryDesignView::initialize()
{
    if(static_cast<OQueryController&>(getController()).getSplitPos() != -1)
    {
        m_aSplitter->SetPosPixel( Point( m_aSplitter->GetPosPixel().X(),static_cast<OQueryController&>(getController()).getSplitPos() ) );
        m_aSplitter->SetSplitPosPixel(static_cast<OQueryController&>(getController()).getSplitPos());
    }
    m_pSelectionBox->initialize();
    reset();
}
 
void OQueryDesignView::resizeDocumentView(tools::Rectangle& _rPlayground)
{
    Point aPlaygroundPos( _rPlayground.TopLeft() );
    Size aPlaygroundSize( _rPlayground.GetSize() );
 
    // calc the split pos, and forward it to the controller
    sal_Int32 nSplitPos = static_cast<OQueryController&>(getController()).getSplitPos();
    if ( 0 != aPlaygroundSize.Height() )
    {
        if  (   ( -1 == nSplitPos )
            ||  ( nSplitPos >= aPlaygroundSize.Height() )
            )
        {
            // let the selection browse box determine an optimal size
            Size aSelectionBoxSize = m_pSelectionBox->CalcOptimalSize( aPlaygroundSize );
            nSplitPos = aPlaygroundSize.Height() - aSelectionBoxSize.Height() - m_aSplitter->GetSizePixel().Height();
            // still an invalid size?
            if ( nSplitPos == -1 || nSplitPos >= aPlaygroundSize.Height() )
                nSplitPos = sal_Int32(aPlaygroundSize.Height()*0.6);
 
            static_cast<OQueryController&>(getController()).setSplitPos(nSplitPos);
        }
 
        if ( !m_bInSplitHandler )
        {   // the resize is triggered by something else than the split handler
            // our main focus is to try to preserve the size of the selectionbrowse box
            Size aSelBoxSize = m_pSelectionBox->GetSizePixel();
            if ( aSelBoxSize.Height() )
            {
                // keep the size of the sel box constant
                nSplitPos = aPlaygroundSize.Height() - m_aSplitter->GetSizePixel().Height() - aSelBoxSize.Height();
 
                // and if the box is smaller than the optimal size, try to do something about it
                Size aSelBoxOptSize = m_pSelectionBox->CalcOptimalSize( aPlaygroundSize );
                if ( aSelBoxOptSize.Height() > aSelBoxSize.Height() )
                {
                    nSplitPos = aPlaygroundSize.Height() - m_aSplitter->GetSizePixel().Height() - aSelBoxOptSize.Height();
                }
 
                static_cast< OQueryController& >(getController()).setSplitPos( nSplitPos );
            }
        }
    }
 
    // normalize the split pos
    Point   aSplitPos( _rPlayground.Left(), nSplitPos );
    Size    aSplitSize( _rPlayground.GetSize().Width(), m_aSplitter->GetSizePixel().Height() );
 
    if( ( aSplitPos.Y() + aSplitSize.Height() ) > ( aPlaygroundSize.Height() ))
        aSplitPos.setY( aPlaygroundSize.Height() - aSplitSize.Height() );
 
    if( aSplitPos.Y() <= aPlaygroundPos.Y() )
        aSplitPos.setY( aPlaygroundPos.Y() + sal_Int32(aPlaygroundSize.Height() * 0.2) );
 
    // position the table
    Size aTableViewSize(aPlaygroundSize.Width(), aSplitPos.Y() - aPlaygroundPos.Y());
    m_pScrollWindow->SetPosSizePixel(aPlaygroundPos, aTableViewSize);
 
    // position the selection browse box
    Point aPos( aPlaygroundPos.X(), aSplitPos.Y() + aSplitSize.Height() );
    m_pSelectionBox->SetPosSizePixel( aPos, Size( aPlaygroundSize.Width(), aPlaygroundSize.Height() - aSplitSize.Height() - aTableViewSize.Height() ));
 
    // set the size of the splitter
    m_aSplitter->SetPosSizePixel( aSplitPos, aSplitSize );
    m_aSplitter->SetDragRectPixel( _rPlayground );
 
    // just for completeness: there is no space left, we occupied it all ...
    _rPlayground.SetPos( _rPlayground.BottomRight() );
    _rPlayground.SetSize( Size( 0, 0 ) );
}
 
void OQueryDesignView::setReadOnly(bool _bReadOnly)
{
    m_pSelectionBox->SetReadOnly(_bReadOnly);
}
 
void OQueryDesignView::clear()
{
    m_pSelectionBox->ClearAll(); // clear the whole selection
    m_pTableView->ClearAll();
}
 
void OQueryDesignView::copy()
{
    if( m_eChildFocus == SELECTION)
        m_pSelectionBox->copy();
}
 
bool OQueryDesignView::isCutAllowed() const
{
    bool bAllowed = false;
    if ( SELECTION == m_eChildFocus )
        bAllowed = m_pSelectionBox->isCutAllowed();
    return bAllowed;
}
 
bool OQueryDesignView::isPasteAllowed() const
{
    bool bAllowed = false;
    if ( SELECTION == m_eChildFocus )
        bAllowed = m_pSelectionBox->isPasteAllowed();
    return bAllowed;
}
 
bool OQueryDesignView::isCopyAllowed() const
{
    bool bAllowed = false;
    if ( SELECTION == m_eChildFocus )
        bAllowed = m_pSelectionBox->isCopyAllowed();
    return bAllowed;
}
 
void OQueryDesignView::stopTimer()
{
    m_pSelectionBox->stopTimer();
}
 
void OQueryDesignView::startTimer()
{
    m_pSelectionBox->startTimer();
}
 
void OQueryDesignView::cut()
{
    if( m_eChildFocus == SELECTION)
    {
        m_pSelectionBox->cut();
        static_cast<OQueryController&>(getController()).setModified(true);
    }
}
 
void OQueryDesignView::paste()
{
    if( m_eChildFocus == SELECTION)
    {
        m_pSelectionBox->paste();
        static_cast<OQueryController&>(getController()).setModified(true);
    }
}
 
void OQueryDesignView::TableDeleted(const OUString& rAliasName)
{
    // message that the table was removed from the window
    m_pSelectionBox->DeleteFields( rAliasName );
    static_cast<OQueryController&>(getController()).InvalidateFeature(ID_BROWSER_ADDTABLE); // inform the view again
}
 
bool OQueryDesignView::HasFieldByAliasName(std::u16string_view rFieldName, OTableFieldDescRef const & rInfo)  const
{
    return m_pSelectionBox->HasFieldByAliasName( rFieldName, rInfo);
}
 
SqlParseError OQueryDesignView::InsertField( const OTableFieldDescRef& rInfo, bool bActivate)
{
    return m_pSelectionBox->InsertField( rInfo, BROWSER_INVALIDID, true/*bVis*/, bActivate ).is() ? eOk : eTooManyColumns;
}
 
sal_Int32 OQueryDesignView::getColWidth(sal_uInt16 _nColPos) const
{
    static sal_Int32 s_nDefaultWidth = GetTextWidth(u"0"_ustr) * 15;
    sal_Int32 nWidth = static_cast<OQueryController&>(getController()).getColWidth(_nColPos);
    if ( !nWidth )
        nWidth = s_nDefaultWidth;
    return nWidth;
}
 
void OQueryDesignView::fillValidFields(std::u16string_view sAliasName, weld::ComboBox& rFieldList)
{
    rFieldList.clear();
 
    bool bAllTables = sAliasName.empty();
 
    OJoinTableView::OTableWindowMap& rTabWins = m_pTableView->GetTabWinMap();
    OUString strCurrentPrefix;
    std::vector< OUString> aFields;
    for (auto const& tabWin : rTabWins)
    {
        OQueryTableWindow* pCurrentWin = static_cast<OQueryTableWindow*>(tabWin.second.get());
        if (bAllTables || (pCurrentWin->GetAliasName() == sAliasName))
        {
            strCurrentPrefix = pCurrentWin->GetAliasName() + ".";
 
            pCurrentWin->EnumValidFields(aFields);
 
            for (auto const& field : aFields)
            {
                if (bAllTables || field.toChar() == '*')
                    rFieldList.append_text(strCurrentPrefix + field);
                else
                    rFieldList.append_text(field);
            }
 
            if (!bAllTables)
                // this means that I came into this block because the table name was exactly what I was looking for so I can end here
                // (and I prevent that fields get added more than once, if a table is repeated in TabWin)
                break;
        }
    }
}
 
bool OQueryDesignView::PreNotify(NotifyEvent& rNEvt)
{
    if (rNEvt.GetType() == NotifyEventType::GETFOCUS)
    {
        if ( m_pSelectionBox && m_pSelectionBox->HasChildPathFocus() )
            m_eChildFocus = SELECTION;
        else
            m_eChildFocus = TABLEVIEW;
    }
 
    return OJoinDesignView::PreNotify(rNEvt);
}
 
// check if the statement is correct when not returning false
bool OQueryDesignView::checkStatement()
{
    bool bRet = true;
    if ( m_pSelectionBox )
        bRet = m_pSelectionBox->Save(); // an error occurred so we return no
    return bRet;
}
 
OUString OQueryDesignView::getStatement()
{
    OQueryController& rController = static_cast<OQueryController&>(getController());
    m_rController.clearError();
    // used for fields which aren't any longer in the statement
    OTableFields& rUnUsedFields = rController.getUnUsedFields();
    OTableFields().swap( rUnUsedFields );
 
    // create the select columns
    sal_uInt32 nFieldcount = 0;
    OTableFields& rFieldList = rController.getTableFieldDesc();
    for (auto const& field : rFieldList)
    {
        if (!field->GetField().isEmpty() && field->IsVisible() )
            ++nFieldcount;
        else if (!field->GetField().isEmpty()            &&
                !field->HasCriteria()                 &&
                field->isNoneFunction()               &&
                field->GetOrderDir() == ORDER_NONE    &&
                !field->IsGroupBy()                   &&
                field->GetFunction().isEmpty() )
            rUnUsedFields.push_back(field);
    }
    if ( !nFieldcount ) // no visible fields so return
    {
        rUnUsedFields = rFieldList;
        return OUString();
    }
 
    OQueryTableView::OTableWindowMap& rTabList   = m_pTableView->GetTabWinMap();
    sal_uInt32 nTabcount        = rTabList.size();
 
    OUString aFieldListStr(GenerateSelectList(this,rFieldList,nTabcount>1));
    if( aFieldListStr.isEmpty() )
        return OUString();
 
    // Exception handling, if no fields have been passed we should not
    // change the tab page
    // TabBarSelectHdl will query the SQL-OUString for STATEMENT_NOFIELDS
    // and trigger an error message
    // ----------------- Build table list ----------------------
 
    const auto& rConnList = m_pTableView->getTableConnections();
    Reference< XConnection> xConnection = rController.getConnection();
    OUString aTableListStr(GenerateFromClause(xConnection,&rTabList,rConnList));
    OSL_ENSURE(!aTableListStr.isEmpty(), "OQueryDesignView::getStatement() : unexpected : have Fields, but no Tables !");
    // if fields exist now, these only can be created by inserting from an already existing table; if on the other hand
    // a table is deleted, also the belonging fields will be deleted -> therefore it CANNOT occur that fields
    // exist but no tables exist (and aFieldListStr has its length, I secure this above)
    OUStringBuffer aHavingStr,aCriteriaListStr;
 
    // ----------------- build the criteria ----------------------
    if (!GenerateCriterias(this,aCriteriaListStr,aHavingStr,rFieldList, nTabcount > 1))
        return OUString();
 
    OUString aJoinCrit;
    GenerateInnerJoinCriterias(xConnection,aJoinCrit,rConnList);
    if(!aJoinCrit.isEmpty())
    {
        OUString aTmp = "( " + aJoinCrit + " )";
        if(!aCriteriaListStr.isEmpty())
        {
            aTmp += C_AND;
        }
        aCriteriaListStr.insert(0, aTmp);
    }
    // ----------------- construct statement  ----------------------
    OUStringBuffer aSqlCmd("SELECT ");
    if(rController.isDistinct())
        aSqlCmd.append(" DISTINCT ");
    aSqlCmd.append(aFieldListStr + " FROM " + aTableListStr);
 
    if (!aCriteriaListStr.isEmpty())
    {
        aSqlCmd.append(" WHERE " + aCriteriaListStr);
    }
    Reference<XDatabaseMetaData> xMeta;
    if ( xConnection.is() )
        xMeta = xConnection->getMetaData();
    bool bUseAlias = nTabcount > 1;
    if ( xMeta.is() )
        bUseAlias = bUseAlias || !xMeta->supportsGroupByUnrelated();
 
    aSqlCmd.append(GenerateGroupBy(this,rFieldList,bUseAlias));
    // ----------------- construct GroupBy and attach ------------
    if(!aHavingStr.isEmpty())
    {
        aSqlCmd.append(" HAVING " + aHavingStr);
    }
    // ----------------- construct sorting and attach ------------
    OUString sOrder;
    SqlParseError eErrorCode = eOk;
    if ( (eErrorCode = GenerateOrder(this,rFieldList,nTabcount > 1,sOrder)) == eOk)
        aSqlCmd.append(sOrder);
    else
    {
        if ( !m_rController.hasError() )
            m_rController.appendError( getParseErrorMessage( eErrorCode ) );
 
        m_rController.displayError();
    }
    // --------------------- Limit Clause -------------------
    {
        const sal_Int64 nLimit = rController.getLimit();
        if( nLimit != -1 )
        {
            aSqlCmd.append( " LIMIT " + OUString::number(nLimit) );
        }
    }
 
    OUString sSQL = aSqlCmd.makeStringAndClear();
    if ( xConnection.is() )
    {
        ::connectivity::OSQLParser& rParser( rController.getParser() );
        OUString sErrorMessage;
        std::unique_ptr<OSQLParseNode> pParseNode( rParser.parseTree( sErrorMessage, sSQL, true ) );
        if (pParseNode)
        {
            OSQLParseNode* pNode = pParseNode->getChild(3)->getChild(1);
            if ( pNode->count() > 1 )
            {
                ::connectivity::OSQLParseNode * pCondition = pNode->getChild(1);
                if ( pCondition ) // no where clause
                {
                    OSQLParseNode::compress(pCondition);
                    OUString sTemp;
                    pParseNode->parseNodeToStr(sTemp,xConnection);
                    sSQL = sTemp;
                }
            }
        }
    }
    return sSQL;
}
 
void OQueryDesignView::setSlotEnabled(sal_Int32 _nSlotId, bool _bEnable)
{
    sal_uInt16 nRow;
    switch (_nSlotId)
    {
        case SID_QUERY_VIEW_FUNCTIONS:
            nRow = BROW_FUNCTION_ROW;
            break;
        case SID_QUERY_VIEW_TABLES:
            nRow = BROW_TABLE_ROW;
            break;
        case SID_QUERY_VIEW_ALIASES:
            nRow = BROW_COLUMNALIAS_ROW;
            break;
        default:
            // ????????????
            nRow = 0;
            break;
    }
    m_pSelectionBox->SetRowVisible(nRow,_bEnable);
    m_pSelectionBox->Invalidate();
}
 
bool OQueryDesignView::isSlotEnabled(sal_Int32 _nSlotId)
{
    sal_uInt16 nRow;
    switch (_nSlotId)
    {
        case SID_QUERY_VIEW_FUNCTIONS:
            nRow = BROW_FUNCTION_ROW;
            break;
        case SID_QUERY_VIEW_TABLES:
            nRow = BROW_TABLE_ROW;
            break;
        case SID_QUERY_VIEW_ALIASES:
            nRow = BROW_COLUMNALIAS_ROW;
            break;
        default:
            // ?????????
            nRow = 0;
            break;
    }
    return m_pSelectionBox->IsRowVisible(nRow);
}
 
void OQueryDesignView::SaveUIConfig()
{
    OQueryController& rCtrl = static_cast<OQueryController&>(getController());
    rCtrl.SaveTabWinsPosSize( &m_pTableView->GetTabWinMap(), m_pScrollWindow->GetHScrollBar().GetThumbPos(), m_pScrollWindow->GetVScrollBar().GetThumbPos() );
    rCtrl.setVisibleRows( m_pSelectionBox->GetNoneVisibleRows() );
    if ( m_aSplitter->GetSplitPosPixel() != 0 )
        rCtrl.setSplitPos( m_aSplitter->GetSplitPosPixel() );
}
 
std::unique_ptr<OSQLParseNode> OQueryDesignView::getPredicateTreeFromEntry(const OTableFieldDescRef& pEntry,
                                                           const OUString& _sCriteria,
                                                           OUString& _rsErrorMessage,
                                                           Reference<XPropertySet>& _rxColumn) const
{
    OSL_ENSURE(pEntry.is(),"Entry is null!");
    if(!pEntry.is())
        return nullptr;
    Reference< XConnection> xConnection = static_cast<OQueryController&>(getController()).getConnection();
    if(!xConnection.is())
        return nullptr;
 
    ::connectivity::OSQLParser& rParser( static_cast<OQueryController&>(getController()).getParser() );
    OQueryTableWindow* pWin = static_cast<OQueryTableWindow*>(pEntry->GetTabWindow());
 
    // special handling for functions
    if ( pEntry->GetFunctionType() & (FKT_OTHER | FKT_AGGREGATE | FKT_NUMERIC) )
    {
        // we have a function here so we have to distinguish the type of return value
        OUString sFunction;
        if ( pEntry->isNumericOrAggregateFunction() )
            sFunction = pEntry->GetFunction().getToken(0, '(');
 
        if ( sFunction.isEmpty() )
            sFunction = pEntry->GetField().getToken(0, '(');
 
        sal_Int32 nType = ::connectivity::OSQLParser::getFunctionReturnType(sFunction,&rParser.getContext());
        if ( nType == DataType::OTHER || (sFunction.isEmpty() && pEntry->isNumericOrAggregateFunction()) )
        {
            // first try the international version
            OUString sSql = "SELECT * FROM x WHERE " + pEntry->GetField() + _sCriteria;
            std::unique_ptr<OSQLParseNode> pParseNode( rParser.parseTree( _rsErrorMessage, sSql, true ) );
            nType = DataType::DOUBLE;
            if (pParseNode)
            {
                OSQLParseNode* pColumnRef = pParseNode->getByRule(OSQLParseNode::column_ref);
                if ( pColumnRef )
                {
                    OTableFieldDescRef aField = new OTableFieldDesc();
                    if ( eOk == FillDragInfo(this,pColumnRef,aField) )
                    {
                        nType = aField->GetDataType();
                    }
                }
            }
        }
 
        Reference<XDatabaseMetaData> xMeta = xConnection->getMetaData();
        rtl::Reference<parse::OParseColumn> pColumn = new parse::OParseColumn( pEntry->GetField(),
                                                                OUString(),
                                                                OUString(),
                                                                OUString(),
                                                                ColumnValue::NULLABLE_UNKNOWN,
                                                                0,
                                                                0,
                                                                nType,
                                                                false,
                                                                false,
                                                                xMeta.is() && xMeta->supportsMixedCaseQuotedIdentifiers(),
                                                                OUString(),
                                                                OUString(),
                                                                OUString());
        _rxColumn = pColumn;
        pColumn->setFunction(true);
        pColumn->setRealName(pEntry->GetField());
    }
    else
    {
        if (pWin)
        {
            Reference<XNameAccess> xColumns = pWin->GetOriginalColumns();
            if (xColumns.is() && xColumns->hasByName(pEntry->GetField()))
                xColumns->getByName(pEntry->GetField()) >>= _rxColumn;
        }
    }
 
    // _rxColumn, if it is a "lookup" column, not a computed column,
    // is guaranteed to be the column taken from the *source* of the column,
    // that is either a table or another query.
    // _rxColumn is *not* taken from the columns of the query we are constructing
    // (and rightfully so, since it may not be part of these columns; SELECT A FROM t WHERE B = foo)
    // If it is a computed column, we just constructed it above, with same Name and RealName.
    // In all cases, we should use the "external" name of the column, not the "RealName";
    // the latter is the name that the column had in the source of the source query.
    // An example: we are designing "SELECT A, B FROM q WHERE C='foo'"
    // q itself is query "SELECT aye AS A, bee as B, cee as C FROM t"
    // We are currently treating the entry "C='foo'"
    // Then _rxColumn has Name "C" and RealName "cee". We should *obviously* use "C", not "cee".
    std::unique_ptr<OSQLParseNode> pParseNode = rParser.predicateTree(  _rsErrorMessage,
                                                        _sCriteria,
                                                        static_cast<OQueryController&>(getController()).getNumberFormatter(),
                                                        _rxColumn,
                                                        false);
    return pParseNode;
}
 
void OQueryDesignView::GetFocus()
{
    OJoinDesignView::GetFocus();
    if ( m_pSelectionBox && !m_pSelectionBox->HasChildPathFocus() )
    {
        // first we have to deactivate the current cell to refill when necessary
        m_pSelectionBox->DeactivateCell();
        m_pSelectionBox->ActivateCell(m_pSelectionBox->GetCurRow(), m_pSelectionBox->GetCurColumnId());
        m_pSelectionBox->GrabFocus();
    }
}
 
void OQueryDesignView::reset()
{
    m_pTableView->ClearAll();
    m_pTableView->ReSync();
}
 
void OQueryDesignView::setNoneVisibleRow(sal_Int32 _nRows)
{
    m_pSelectionBox->SetNoneVisibleRow(_nRows);
}
 
void OQueryDesignView::initByFieldDescriptions( const Sequence< PropertyValue >& i_rFieldDescriptions )
{
    OQueryController& rController = static_cast< OQueryController& >( getController() );
 
    m_pSelectionBox->PreFill();
    m_pSelectionBox->SetReadOnly( rController.isReadOnly() );
    m_pSelectionBox->Fill();
 
    for ( auto const & field : i_rFieldDescriptions )
    {
        ::rtl::Reference< OTableFieldDesc > pField( new OTableFieldDesc() );
        pField->Load( field, true );
        InsertField( pField, false );
    }
 
    rController.ClearUndoManager();
    m_pSelectionBox->Invalidate();
}
 
bool OQueryDesignView::initByParseIterator( ::dbtools::SQLExceptionInfo* _pErrorInfo )
{
    SqlParseError eErrorCode = eNativeMode;
    m_rController.clearError();
 
    try
    {
        eErrorCode = InitFromParseNodeImpl( this, m_pSelectionBox );
 
        if ( eErrorCode != eOk )
        {
            if ( !m_rController.hasError() )
                m_rController.appendError( getParseErrorMessage( eErrorCode ) );
 
            if ( _pErrorInfo )
            {
                *_pErrorInfo = m_rController.getError();
            }
            else
            {
                m_rController.displayError();
            }
        }
    }
    catch ( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("dbaccess");
    }
    return eErrorCode == eOk;
}
 
// Utility function for fillFunctionInfo
namespace {
    sal_Int32 char_datatype(const::connectivity::OSQLParseNode* pDataType, const unsigned int offset) {
        int cnt = pDataType->count() - offset;
        if ( cnt < 0 )
        {
            OSL_FAIL("internal error in decoding character datatype specification");
            return DataType::VARCHAR;
        }
        else if ( cnt == 0 )
        {
            if ( offset == 0 )
            {
                // The datatype is the node itself
                if ( SQL_ISTOKENOR2 (pDataType, CHARACTER, CHAR) )
                    return DataType::CHAR;
                else if ( SQL_ISTOKEN (pDataType, VARCHAR) )
                    return DataType::VARCHAR;
                else if ( SQL_ISTOKEN (pDataType, CLOB) )
                    return DataType::CLOB;
                else
                {
                    OSL_FAIL("unknown/unexpected token in decoding character datatype specification");
                    return DataType::VARCHAR;
                }
            }
            else
            {
                // No child left to read!
                OSL_FAIL("incomplete datatype in decoding character datatype specification");
                return DataType::VARCHAR;
            }
        }
 
        if ( SQL_ISTOKEN(pDataType->getChild(offset), NATIONAL) )
            return char_datatype(pDataType, offset+1);
        else if ( SQL_ISTOKENOR3(pDataType->getChild(offset), CHARACTER, CHAR, NCHAR) )
        {
            if ( cnt > 2 && SQL_ISTOKEN(pDataType->getChild(offset+1), LARGE) && SQL_ISTOKEN(pDataType->getChild(offset+2), OBJECT) )
                return DataType::CLOB;
            else if ( cnt > 1 && SQL_ISTOKEN(pDataType->getChild(offset+1), VARYING) )
                return DataType::VARCHAR;
            else
                return DataType::CHAR;
        }
        else if ( SQL_ISTOKEN (pDataType->getChild(offset), VARCHAR) )
            return DataType::VARCHAR;
        else if ( SQL_ISTOKENOR2 (pDataType->getChild(offset), CLOB, NCLOB) )
            return DataType::CLOB;
 
        OSL_FAIL("unrecognised character datatype");
        return DataType::VARCHAR;
    }
}
 
// Try to guess the type of an expression in simple cases.
// Originally meant to be called only on a function call (hence the misnomer),
// but now tries to do the best it can also in other cases.
// Don't completely rely on fillFunctionInfo,
// it won't look at the function's arguments to find the return type
// (in particular, in the case of general_set_fct,
//  the return type is the type of the argument;
//  if that is (as is typical) a column reference,
//  it is the type of the column).
// TODO: There is similar "guess the expression's type" code in several places:
//       SelectionBrowseBox.cxx: OSelectionBrowseBox::saveField
//       QueryDesignView.cxx: InstallFields, GetOrderCriteria, GetGroupCriteria
//       If possible, they should be factorised into this function
//       (which should then be renamed...)
 
void OQueryDesignView::fillFunctionInfo(  const ::connectivity::OSQLParseNode* pNode
                                        ,const OUString& sFunctionTerm
                                        ,OTableFieldDescRef& aInfo)
{
    // get the type of the expression, as far as easily possible
    OQueryController& rController = static_cast<OQueryController&>(getController());
    sal_Int32 nDataType = DataType::DOUBLE;
    switch(pNode->getNodeType())
    {
    case SQLNodeType::Concat:
    case SQLNodeType::String:
        nDataType = DataType::VARCHAR;
        break;
    case SQLNodeType::IntNum:
        nDataType = DataType::INTEGER;
        break;
    case SQLNodeType::ApproxNum:
        nDataType = DataType::DOUBLE;
        break;
    case SQLNodeType::AccessDate:
        nDataType = DataType::TIMESTAMP;
        break;
    case SQLNodeType::Equal:
    case SQLNodeType::Less:
    case SQLNodeType::Great:
    case SQLNodeType::LessEq:
    case SQLNodeType::GreatEq:
    case SQLNodeType::NotEqual:
        nDataType = DataType::BOOLEAN;
        break;
    case SQLNodeType::Name:
    case SQLNodeType::ListRule:
    case SQLNodeType::CommaListRule:
    case SQLNodeType::Keyword:
    case SQLNodeType::Punctuation:
        OSL_FAIL("Unexpected SQL Node Type");
        break;
    case SQLNodeType::Rule:
        switch(pNode->getKnownRuleID())
        {
        case OSQLParseNode::select_statement:
        case OSQLParseNode::table_exp:
        case OSQLParseNode::table_ref_commalist:
        case OSQLParseNode::table_ref:
        case OSQLParseNode::catalog_name:
        case OSQLParseNode::schema_name:
        case OSQLParseNode::table_name:
        case OSQLParseNode::opt_column_commalist:
        case OSQLParseNode::column_commalist:
        case OSQLParseNode::column_ref_commalist:
        case OSQLParseNode::column_ref:
        case OSQLParseNode::opt_order_by_clause:
        case OSQLParseNode::ordering_spec_commalist:
        case OSQLParseNode::ordering_spec:
        case OSQLParseNode::opt_asc_desc:
        case OSQLParseNode::where_clause:
        case OSQLParseNode::opt_where_clause:
        case OSQLParseNode::opt_escape:
        case OSQLParseNode::scalar_exp_commalist:
        case OSQLParseNode::scalar_exp: // Seems to never be generated?
        case OSQLParseNode::parameter_ref:
        case OSQLParseNode::parameter:
        case OSQLParseNode::range_variable:
        case OSQLParseNode::delete_statement_positioned:
        case OSQLParseNode::delete_statement_searched:
        case OSQLParseNode::update_statement_positioned:
        case OSQLParseNode::update_statement_searched:
        case OSQLParseNode::assignment_commalist:
        case OSQLParseNode::assignment:
        case OSQLParseNode::insert_statement:
        case OSQLParseNode::insert_atom_commalist:
        case OSQLParseNode::insert_atom:
        case OSQLParseNode::from_clause:
        case OSQLParseNode::qualified_join:
        case OSQLParseNode::cross_union:
        case OSQLParseNode::select_sublist:
        case OSQLParseNode::join_type:
        case OSQLParseNode::named_columns_join:
        case OSQLParseNode::joined_table:
        case OSQLParseNode::sql_not:
        case OSQLParseNode::manipulative_statement:
        case OSQLParseNode::value_exp_commalist:
        case OSQLParseNode::union_statement:
        case OSQLParseNode::outer_join_type:
        case OSQLParseNode::selection:
        case OSQLParseNode::base_table_def:
        case OSQLParseNode::base_table_element_commalist:
        case OSQLParseNode::data_type:
        case OSQLParseNode::column_def:
        case OSQLParseNode::table_node:
        case OSQLParseNode::as_clause:
        case OSQLParseNode::opt_as:
        case OSQLParseNode::op_column_commalist:
        case OSQLParseNode::table_primary_as_range_column:
        case OSQLParseNode::character_string_type:
        case OSQLParseNode::comparison:
            OSL_FAIL("Unexpected SQL RuleID");
            break;
        case OSQLParseNode::column:
        case OSQLParseNode::column_val:
            OSL_FAIL("Cannot guess column type");
            break;
        case OSQLParseNode::values_or_query_spec:
            OSL_FAIL("Cannot guess VALUES type");
            break;
        case OSQLParseNode::derived_column:
            OSL_FAIL("Cannot guess computed column type");
            break;
        case OSQLParseNode::subquery:
            OSL_FAIL("Cannot guess subquery return type");
            break;
        case OSQLParseNode::search_condition:
        case OSQLParseNode::comparison_predicate:
        case OSQLParseNode::between_predicate:
        case OSQLParseNode::like_predicate:
        case OSQLParseNode::test_for_null:
        case OSQLParseNode::boolean_term:
        case OSQLParseNode::boolean_primary:
        case OSQLParseNode::in_predicate:
        case OSQLParseNode::existence_test:
        case OSQLParseNode::unique_test:
        case OSQLParseNode::all_or_any_predicate:
        case OSQLParseNode::join_condition:
        case OSQLParseNode::boolean_factor:
        case OSQLParseNode::comparison_predicate_part_2:
        case OSQLParseNode::parenthesized_boolean_value_expression:
        case OSQLParseNode::other_like_predicate_part_2:
        case OSQLParseNode::between_predicate_part_2:
            nDataType = DataType::BOOLEAN;
            break;
        case OSQLParseNode::num_value_exp:
        case OSQLParseNode::extract_exp:
        case OSQLParseNode::term:
        case OSQLParseNode::factor:
            // Might by an integer or a float; take the most generic
            nDataType = DataType::DOUBLE;
            break;
        case OSQLParseNode::value_exp_primary:
        case OSQLParseNode::value_exp:
        case OSQLParseNode::odbc_call_spec:
            // Really, we don't know. Let the default.
            break;
        case OSQLParseNode::position_exp:
        case OSQLParseNode::length_exp:
            nDataType = DataType::INTEGER;
            break;
        case OSQLParseNode::char_value_exp:
        case OSQLParseNode::char_value_fct:
        case OSQLParseNode::fold:
        case OSQLParseNode::char_substring_fct:
        case OSQLParseNode::char_factor:
        case OSQLParseNode::concatenation:
            nDataType = DataType::VARCHAR;
            break;
        case OSQLParseNode::datetime_primary:
            nDataType = DataType::TIMESTAMP;
            break;
        case OSQLParseNode::bit_value_fct:
            nDataType = DataType::BINARY;
            break;
        case OSQLParseNode::general_set_fct: // May depend on argument; ignore that for now
        case OSQLParseNode::set_fct_spec:
        {
            if (pNode->count() == 0)
            {
                // This is not a function call, no sense to continue with a function return type lookup
                OSL_FAIL("Got leaf SQL node where non-leaf expected");
                break;
            }
            const OSQLParseNode* pFunctionName = pNode->getChild(0);
            if ( SQL_ISPUNCTUATION(pFunctionName,"{") )
            {
                if ( pNode->count() == 3 )
                    return fillFunctionInfo( pNode->getChild(1), sFunctionTerm, aInfo );
                else
                    OSL_FAIL("ODBC escape not in recognised form");
                break;
            }
            else
            {
                if ( SQL_ISRULEOR2(pNode,length_exp,char_value_fct) )
                    pFunctionName = pFunctionName->getChild(0);
 
                OUString sFunctionName = pFunctionName->getTokenValue();
                if ( sFunctionName.isEmpty() )
                    sFunctionName = OStringToOUString(OSQLParser::TokenIDToStr(pFunctionName->getTokenID()),RTL_TEXTENCODING_UTF8);
 
                nDataType = OSQLParser::getFunctionReturnType(
                    sFunctionName
                    ,&rController.getParser().getContext());
            }
            break;
        }
        case OSQLParseNode::odbc_fct_spec:
        {
            if (pNode->count() != 2)
            {
                OSL_FAIL("interior of ODBC escape not in recognised shape");
                break;
            }
 
            const OSQLParseNode* const pEscapeType = pNode->getChild(0);
            if (SQL_ISTOKEN(pEscapeType, TS))
                nDataType = DataType::TIMESTAMP;
            else if (SQL_ISTOKEN(pEscapeType, D))
                nDataType = DataType::DATE;
            else if (SQL_ISTOKEN(pEscapeType, T))
                nDataType = DataType::TIME;
            else if (SQL_ISTOKEN(pEscapeType, FN))
                return fillFunctionInfo( pNode->getChild(1), sFunctionTerm, aInfo );
            else
                OSL_FAIL("Unknown ODBC escape");
            break;
        }
        case OSQLParseNode::cast_spec:
        {
            if ( pNode->count() != 6 || !SQL_ISTOKEN(pNode->getChild(3), AS) )
            {
                OSL_FAIL("CAST not in recognised shape");
                break;
            }
            const OSQLParseNode *pCastTarget = pNode->getChild(4);
            if ( SQL_ISTOKENOR2(pCastTarget, INTEGER, INT) )
                nDataType = DataType::INTEGER;
            else if ( SQL_ISTOKEN(pCastTarget, SMALLINT) )
                nDataType = DataType::SMALLINT;
            else if ( SQL_ISTOKEN(pCastTarget, BIGINT) )
                nDataType = DataType::BIGINT;
            else if ( SQL_ISTOKEN(pCastTarget, FLOAT) )
                nDataType = DataType::FLOAT;
            else if ( SQL_ISTOKEN(pCastTarget, REAL) )
                nDataType = DataType::REAL;
            else if ( SQL_ISTOKEN(pCastTarget, DOUBLE) )
                nDataType = DataType::DOUBLE;
            else if ( SQL_ISTOKEN(pCastTarget, BOOLEAN) )
                nDataType = DataType::BOOLEAN;
            else if ( SQL_ISTOKEN(pCastTarget, DATE) )
                nDataType = DataType::DATE;
            else if ( pCastTarget->count() > 0 )
            {
                const OSQLParseNode *pDataType = pCastTarget->getChild(0);
                while (pDataType->count() > 0)
                {
                    pCastTarget = pDataType;
                    pDataType = pDataType->getChild(0);
                }
                if ( SQL_ISTOKEN (pDataType, TIME) )
                    nDataType = DataType::TIME;
                else if ( SQL_ISTOKEN (pDataType, TIMESTAMP) )
                    nDataType = DataType::TIMESTAMP;
                else if ( SQL_ISTOKENOR3 (pDataType, CHARACTER, CHAR, NCHAR) )
                    nDataType = char_datatype(pCastTarget, 0);
                else if ( SQL_ISTOKEN (pDataType, VARCHAR) )
                    nDataType = DataType::VARCHAR;
                else if ( SQL_ISTOKEN (pDataType, CLOB) )
                    nDataType = DataType::CLOB;
                else if ( SQL_ISTOKEN (pDataType, NATIONAL) )
                    nDataType = char_datatype(pCastTarget, 1);
                else if ( SQL_ISTOKEN (pDataType, BINARY) )
                {
                    if ( pCastTarget->count() > 2 && SQL_ISTOKEN(pCastTarget->getChild(1), LARGE) && SQL_ISTOKEN(pCastTarget->getChild(2), OBJECT) )
                        nDataType = DataType::BLOB;
                    else if ( pCastTarget->count() > 1 && SQL_ISTOKEN(pCastTarget->getChild(1), VARYING) )
                        nDataType = DataType::VARBINARY;
                    else
                        nDataType = DataType::BINARY;
                }
                else if ( SQL_ISTOKEN (pDataType, VARBINARY) )
                    nDataType = DataType::VARBINARY;
                else if ( SQL_ISTOKEN (pDataType, BLOB) )
                    nDataType = DataType::BLOB;
                else if ( SQL_ISTOKEN (pDataType, NUMERIC) )
                    nDataType = DataType::NUMERIC;
                else if ( SQL_ISTOKENOR2 (pDataType, DECIMAL, DEC) )
                    nDataType = DataType::DECIMAL;
                else if ( SQL_ISTOKEN (pDataType, FLOAT) )
                    nDataType = DataType::FLOAT;
                else if ( SQL_ISTOKEN (pDataType, DOUBLE) )
                    nDataType = DataType::DOUBLE;
                else if ( SQL_ISTOKEN (pDataType, INTERVAL) )
                    // Not in DataType published constant (because not in JDBC...)
                    nDataType = DataType::VARCHAR;
                else
                    OSL_FAIL("Failed to decode CAST target");
            }
            else
                OSL_FAIL("Could not decipher CAST target");
            break;
        }
        default:
            OSL_FAIL("Unknown SQL RuleID");
            break;
        }
        break;
    default:
        OSL_FAIL("Unknown SQL Node Type");
        break;
    }
 
    aInfo->SetDataType(nDataType);
    aInfo->SetFieldType(TAB_NORMAL_FIELD);
    aInfo->SetField(sFunctionTerm);
    aInfo->SetTabWindow(nullptr);
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V1037 Two or more case-branches perform the same actions. Check lines: 2361, 2367, 2370

V1048 The 'eJoinType' variable was assigned the same value.

V1048 The 'nDataType' variable was assigned the same value.

V1048 The 'nDataType' variable was assigned the same value.

V1048 The 'nDataType' variable was assigned the same value.

V1048 The 'nDataType' variable was assigned the same value.