/* -*- 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 <connectivity/dbexception.hxx>
#include <connectivity/sqliterator.hxx>
#include <connectivity/sdbcx/VTable.hxx>
#include <connectivity/sqlparse.hxx>
#include <sqlbison.hxx>
#include <connectivity/dbtools.hxx>
#include <connectivity/sqlerror.hxx>
#include <com/sun/star/sdbc/ColumnValue.hpp>
#include <com/sun/star/sdbc/DataType.hpp>
#include <com/sun/star/sdbc/XRow.hpp>
#include <com/sun/star/sdb/XQueriesSupplier.hpp>
#include <com/sun/star/sdb/ErrorCondition.hpp>
#ifdef SQL_TEST_PARSETREEITERATOR
#include <iostream>
#endif
#include <connectivity/PColumn.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <TConnection.hxx>
#include <comphelper/types.hxx>
#include <connectivity/dbmetadata.hxx>
#include <com/sun/star/sdb/SQLFilterOperator.hpp>
#include <o3tl/safeint.hxx>
#include <sal/log.hxx>

#include <iterator>
#include <memory>
#include <utility>

using namespace ::comphelper;
using namespace ::connectivity;
using namespace ::connectivity::sdbcx;
using namespace ::dbtools;
using namespace ::connectivity::parse;
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::sdbcx;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::sdbc;
using namespace ::com::sun::star::sdb;

namespace connectivity
{
    struct OSQLParseTreeIteratorImpl
    {
        std::vector< TNodePair >      m_aJoinConditions;
        Reference< XConnection >        m_xConnection;
        Reference< XDatabaseMetaData >  m_xDatabaseMetaData;
        Reference< XNameAccess >        m_xTableContainer;
        Reference< XNameAccess >        m_xQueryContainer;

        std::shared_ptr< OSQLTables >   m_pTables;      // all tables which participate in the SQL statement
        std::shared_ptr< OSQLTables >   m_pSubTables;   // all tables from sub queries not the tables from the select tables
        std::shared_ptr< QueryNameSet > m_pForbiddenQueryNames;

        TraversalParts                  m_nIncludeMask;

        bool                            m_bIsCaseSensitive;

        OSQLParseTreeIteratorImpl( const Reference< XConnection >& _rxConnection, const Reference< XNameAccess >& _rxTables )
            :m_xConnection( _rxConnection )
            ,m_nIncludeMask( TraversalParts::All )
            ,m_bIsCaseSensitive( true )
        {
            OSL_PRECOND( m_xConnection.is(), "OSQLParseTreeIteratorImpl::OSQLParseTreeIteratorImpl: invalid connection!" );
            m_xDatabaseMetaData = m_xConnection->getMetaData();

            m_bIsCaseSensitive = m_xDatabaseMetaData.is() && m_xDatabaseMetaData->supportsMixedCaseQuotedIdentifiers();
            m_pTables = std::make_shared<OSQLTables>( UStringMixLess(m_bIsCaseSensitive) );
            m_pSubTables = std::make_shared<OSQLTables>( UStringMixLess(m_bIsCaseSensitive) );

            m_xTableContainer = _rxTables;

            DatabaseMetaData aMetaData( m_xConnection );
            if ( aMetaData.supportsSubqueriesInFrom() )
            {
                // connections might support the XQueriesSupplier interface, if they implement the css.sdb.Connection
                // service
                Reference< XQueriesSupplier > xSuppQueries( m_xConnection, UNO_QUERY );
                if ( xSuppQueries.is() )
                    m_xQueryContainer = xSuppQueries->getQueries();
            }
        }

    public:
        bool    isQueryAllowed( const OUString& _rQueryName )
        {
            if ( !m_pForbiddenQueryNames )
                return true;
            if ( m_pForbiddenQueryNames->find( _rQueryName ) == m_pForbiddenQueryNames->end() )
                return true;
            return false;
        }
    };

    namespace {

    /** helper class for temporarily adding a query name to a list of forbidden query names
    */
    class ForbidQueryName
    {
        std::shared_ptr< QueryNameSet >&    m_rpAllForbiddenNames;
        OUString                         m_sForbiddenQueryName;

    public:
        ForbidQueryName( OSQLParseTreeIteratorImpl& _rIteratorImpl, OUString _aForbiddenQueryName )
            :m_rpAllForbiddenNames( _rIteratorImpl.m_pForbiddenQueryNames )
            ,m_sForbiddenQueryName(std::move( _aForbiddenQueryName ))
        {
            if ( !m_rpAllForbiddenNames )
                m_rpAllForbiddenNames = std::make_shared<QueryNameSet>();
            m_rpAllForbiddenNames->insert( m_sForbiddenQueryName );
        }

        ~ForbidQueryName()
        {
            m_rpAllForbiddenNames->erase( m_sForbiddenQueryName );
        }
    };

    }
}

OSQLParseTreeIterator::OSQLParseTreeIterator(const Reference< XConnection >& _rxConnection,
                                             const Reference< XNameAccess >& _rxTables,
                                             const OSQLParser& _rParser )
    :m_rParser( _rParser )
    ,m_pImpl( new OSQLParseTreeIteratorImpl( _rxConnection, _rxTables ) )
{
    setParseTree(nullptr);
}


OSQLParseTreeIterator::OSQLParseTreeIterator( const OSQLParseTreeIterator& _rParentIterator, const OSQLParser& _rParser, const OSQLParseNode* pRoot )
    :m_rParser( _rParser )
    ,m_pImpl( new OSQLParseTreeIteratorImpl( _rParentIterator.m_pImpl->m_xConnection, _rParentIterator.m_pImpl->m_xTableContainer ) )
{
    m_pImpl->m_pForbiddenQueryNames = _rParentIterator.m_pImpl->m_pForbiddenQueryNames;
    setParseTree( pRoot );
}


OSQLParseTreeIterator::~OSQLParseTreeIterator()
{
    dispose();
}


const OSQLTables& OSQLParseTreeIterator::getTables() const
{
    return *m_pImpl->m_pTables;
}


bool OSQLParseTreeIterator::isCaseSensitive() const
{
    return m_pImpl->m_bIsCaseSensitive;
}


void OSQLParseTreeIterator::dispose()
{
    m_aSelectColumns    = nullptr;
    m_aGroupColumns     = nullptr;
    m_aOrderColumns     = nullptr;
    m_aParameters       = nullptr;
    m_pImpl->m_xTableContainer  = nullptr;
    m_pImpl->m_xDatabaseMetaData = nullptr;
    m_aCreateColumns    = nullptr;
    m_pImpl->m_pTables->clear();
    m_pImpl->m_pSubTables->clear();
}

void OSQLParseTreeIterator::setParseTree(const OSQLParseNode * pNewParseTree)
{
    m_pImpl->m_pTables->clear();
    m_pImpl->m_pSubTables->clear();

    m_aSelectColumns = new OSQLColumns();
    m_aGroupColumns = new OSQLColumns();
    m_aOrderColumns = new OSQLColumns();
    m_aParameters    = new OSQLColumns();
    m_aCreateColumns = new OSQLColumns();

    m_pParseTree = pNewParseTree;
    if (!m_pParseTree)
    {
        m_eStatementType = OSQLStatementType::Unknown;
        return;
    }

    // If m_pParseTree, but no connection then return
    if ( !m_pImpl->m_xTableContainer.is() )
        return;

    m_xErrors.reset();


    // Determine statement type ...
    if (SQL_ISRULE(m_pParseTree,select_statement) || SQL_ISRULE(m_pParseTree,union_statement) )
    {
        m_eStatementType = OSQLStatementType::Select;
    }
    else if (SQL_ISRULE(m_pParseTree,insert_statement))
    {
        m_eStatementType = OSQLStatementType::Insert;
    }
    else if (SQL_ISRULE(m_pParseTree,update_statement_searched))
    {
        m_eStatementType = OSQLStatementType::Update;
    }
    else if (SQL_ISRULE(m_pParseTree,delete_statement_searched))
    {
        m_eStatementType = OSQLStatementType::Delete;
    }
    else if (m_pParseTree->count() == 3 && SQL_ISRULE(m_pParseTree->getChild(1),odbc_call_spec))
    {
        m_eStatementType = OSQLStatementType::OdbcCall;
    }
    else if (SQL_ISRULE(m_pParseTree->getChild(0),base_table_def))
    {
        m_eStatementType = OSQLStatementType::CreateTable;
        m_pParseTree = m_pParseTree->getChild(0);
    }
    else
    {
        m_eStatementType = OSQLStatementType::Unknown;
        //aIteratorStatus.setInvalidStatement();
        return;
    }
}


namespace
{

    void impl_getRowString( const Reference< XRow >& _rxRow, const sal_Int32 _nColumnIndex, OUString& _out_rString )
    {
        _out_rString = _rxRow->getString( _nColumnIndex );
        if ( _rxRow->wasNull() )
            _out_rString.clear();
    }


    OUString lcl_findTableInMetaData(
        const Reference< XDatabaseMetaData >& _rxDBMeta, const OUString& _rCatalog,
        const OUString& _rSchema, const OUString& _rTableName )
    {
        OUString sComposedName;

        static constexpr OUString s_sWildcard = u"%"_ustr ;

        // we want all catalogues, all schemas, all tables
        Sequence< OUString > sTableTypes { u"VIEW"_ustr, u"TABLE"_ustr, s_sWildcard }; // this last one just to be sure to include anything else...

        if ( _rxDBMeta.is() )
        {
            sComposedName.clear();

            Reference< XResultSet> xRes = _rxDBMeta->getTables(
                !_rCatalog.isEmpty() ? Any( _rCatalog ) : Any(), !_rSchema.isEmpty() ? _rSchema : s_sWildcard, _rTableName, sTableTypes );

            Reference< XRow > xCurrentRow( xRes, UNO_QUERY );
            if ( xCurrentRow.is() && xRes->next() )
            {
                OUString sCatalog, sSchema, sName;

                impl_getRowString( xCurrentRow, 1, sCatalog );
                impl_getRowString( xCurrentRow, 2, sSchema );
                impl_getRowString( xCurrentRow, 3, sName );

                sComposedName = ::dbtools::composeTableName(
                    _rxDBMeta,
                    sCatalog,
                    sSchema,
                    sName,
                    false,
                    ::dbtools::EComposeRule::InDataManipulation
                );
            }
        }
        return sComposedName;
    }
}


void OSQLParseTreeIterator::impl_getQueryParameterColumns( const OSQLTable& _rQuery  )
{
    if ( !( m_pImpl->m_nIncludeMask & TraversalParts::Parameters ) )
        // parameters not to be included in the traversal
        return;

    ::rtl::Reference pSubQueryParameterColumns( new OSQLColumns() );

    // get the command and the EscapeProcessing properties from the sub query
    OUString sSubQueryCommand;
    bool bEscapeProcessing = false;
    try
    {
        Reference< XPropertySet > xQueryProperties( _rQuery, UNO_QUERY_THROW );
        OSL_VERIFY( xQueryProperties->getPropertyValue( OMetaConnection::getPropMap().getNameByIndex( PROPERTY_ID_COMMAND ) ) >>= sSubQueryCommand );
        OSL_VERIFY( xQueryProperties->getPropertyValue( OMetaConnection::getPropMap().getNameByIndex( PROPERTY_ID_ESCAPEPROCESSING ) ) >>= bEscapeProcessing );
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("connectivity.parse");
    }

    // parse the sub query
    do {

    if ( !bEscapeProcessing || ( sSubQueryCommand.isEmpty() ) )
        break;

    OUString sError;
    std::unique_ptr< OSQLParseNode > pSubQueryNode( const_cast< OSQLParser& >( m_rParser ).parseTree( sError, sSubQueryCommand ) );
    if (!pSubQueryNode)
        break;

    OSQLParseTreeIterator aSubQueryIterator( *this, m_rParser, pSubQueryNode.get() );
    aSubQueryIterator.impl_traverse( TraversalParts::Parameters | TraversalParts::SelectColumns );
        // SelectColumns might also contain parameters #i77635#
    pSubQueryParameterColumns = aSubQueryIterator.getParameters();
    aSubQueryIterator.dispose();

    } while ( false );

    // copy the parameters of the sub query to our own parameter array
    m_aParameters->insert( m_aParameters->end(), pSubQueryParameterColumns->begin(), pSubQueryParameterColumns->end() );
}


OSQLTable OSQLParseTreeIterator::impl_locateRecordSource( const OUString& _rComposedName )
{
    if ( _rComposedName.isEmpty() )
    {
        SAL_WARN( "connectivity.parse", "OSQLParseTreeIterator::impl_locateRecordSource: no object name at all?" );
        return OSQLTable();
    }

    OSQLTable aReturn;
    OUString sComposedName( _rComposedName );

    try
    {
        OUString sCatalog, sSchema, sName;
        qualifiedNameComponents( m_pImpl->m_xDatabaseMetaData, sComposedName, sCatalog, sSchema, sName, ::dbtools::EComposeRule::InDataManipulation );

        // check whether there is a query with the given name
        bool bQueryDoesExist = m_pImpl->m_xQueryContainer.is() && m_pImpl->m_xQueryContainer->hasByName( sComposedName );

        // check whether the table container contains an object with the given name
        if ( !bQueryDoesExist && !m_pImpl->m_xTableContainer->hasByName( sComposedName ) )
            sComposedName = lcl_findTableInMetaData( m_pImpl->m_xDatabaseMetaData, sCatalog, sSchema, sName );
        bool bTableDoesExist = m_pImpl->m_xTableContainer->hasByName( sComposedName );

        // now obtain the object

        // if we're creating a table, and there already is a table or query with the same name,
        // this is worth an error
        if ( OSQLStatementType::CreateTable == m_eStatementType )
        {
            if ( bQueryDoesExist )
                impl_appendError( IParseContext::ErrorCode::InvalidQueryExist, &sName );
            else if ( bTableDoesExist )
                impl_appendError( IParseContext::ErrorCode::InvalidTableExist, &sName );
            else
                aReturn = impl_createTableObject( sName, sCatalog, sSchema );
        }
        else
        {
            // queries win over tables, so if there's a query with this name, take this, no matter if
            // there's a table, too
            if ( bQueryDoesExist )
            {
                if  ( !m_pImpl->isQueryAllowed( sComposedName ) )
                {
                    impl_appendError( m_rParser.getErrorHelper().getSQLException( sdb::ErrorCondition::PARSER_CYCLIC_SUB_QUERIES, nullptr ) );
                    return nullptr;
                }

                m_pImpl->m_xQueryContainer->getByName( sComposedName ) >>= aReturn;

                // collect the parameters from the sub query
                ForbidQueryName aForbidName( *m_pImpl, sComposedName );
                impl_getQueryParameterColumns( aReturn );
            }
            else if ( bTableDoesExist )
                m_pImpl->m_xTableContainer->getByName( sComposedName ) >>= aReturn;
            else
            {
                if ( m_pImpl->m_xQueryContainer.is() )
                    // the connection on which we're working supports sub queries in from (else
                    // m_xQueryContainer would not have been set), so emit a better error message
                    impl_appendError( IParseContext::ErrorCode::InvalidTableOrQuery, &sName );
                else
                    impl_appendError( IParseContext::ErrorCode::InvalidTableNosuch, &sName );
            }
        }
    }
    catch(Exception&)
    {
        impl_appendError( IParseContext::ErrorCode::InvalidTableNosuch, &sComposedName );
    }

    return aReturn;
}


void OSQLParseTreeIterator::traverseOneTableName( OSQLTables& _rTables,const OSQLParseNode * pTableName, const OUString & rTableRange )
{
    if ( !( m_pImpl->m_nIncludeMask & TraversalParts::TableNames ) )
        // tables should not be included in the traversal
        return;

    OSL_ENSURE(pTableName != nullptr,"OSQLParseTreeIterator::traverseOneTableName: pTableName == NULL");

    Any aCatalog;
    OUString aSchema,aTableName,aComposedName;
    OUString aTableRange(rTableRange);

    // Get table name
    OSQLParseNode::getTableComponents(pTableName,aCatalog,aSchema,aTableName,m_pImpl->m_xDatabaseMetaData);

    // create the composed name like DOMAIN.USER.TABLE1
    aComposedName = ::dbtools::composeTableName(m_pImpl->m_xDatabaseMetaData,
                                aCatalog.hasValue() ? ::comphelper::getString(aCatalog) : OUString(),
                                aSchema,
                                aTableName,
                                false,
                                ::dbtools::EComposeRule::InDataManipulation);

    // if there is no alias for the table name assign the original name to it
    if ( aTableRange.isEmpty() )
        aTableRange = aComposedName;

    // get the object representing this table/query
    OSQLTable aTable = impl_locateRecordSource( aComposedName );
    if ( aTable.is() )
        _rTables[ aTableRange ] = std::move(aTable);
}

void OSQLParseTreeIterator::impl_fillJoinConditions(const OSQLParseNode* i_pJoinCondition)
{
    if (i_pJoinCondition->count() == 3 &&   // Expression with brackets
        SQL_ISPUNCTUATION(i_pJoinCondition->getChild(0),"(") &&
        SQL_ISPUNCTUATION(i_pJoinCondition->getChild(2),")"))
    {
        impl_fillJoinConditions(i_pJoinCondition->getChild(1));
    }
    else if (SQL_ISRULEOR2(i_pJoinCondition,search_condition,boolean_term)  &&  // AND/OR logic operation:
             i_pJoinCondition->count() == 3)
    {
        // Only allow AND logic operation
        if ( SQL_ISTOKEN(i_pJoinCondition->getChild(1),AND) )
        {
            impl_fillJoinConditions(i_pJoinCondition->getChild(0));
            impl_fillJoinConditions(i_pJoinCondition->getChild(1));
        }
    }
    else if (SQL_ISRULE(i_pJoinCondition,comparison_predicate))
    {
        // only the comparison of columns is allowed
        OSL_ENSURE(i_pJoinCondition->count() == 3,"OQueryDesignView::InsertJoinConnection: error in the parse tree");
        if (SQL_ISRULE(i_pJoinCondition->getChild(0),column_ref) &&
              SQL_ISRULE(i_pJoinCondition->getChild(2),column_ref) &&
               i_pJoinCondition->getChild(1)->getNodeType() == SQLNodeType::Equal)
        {
            m_pImpl->m_aJoinConditions.push_back( TNodePair(i_pJoinCondition->getChild(0),i_pJoinCondition->getChild(2)) );
        }
    }
}

std::vector< TNodePair >& OSQLParseTreeIterator::getJoinConditions() const
{
    return m_pImpl->m_aJoinConditions;
}

void OSQLParseTreeIterator::getQualified_join( OSQLTables& _rTables, const OSQLParseNode *pTableRef, OUString& aTableRange )
{
    OSL_PRECOND( SQL_ISRULE( pTableRef, cross_union ) || SQL_ISRULE( pTableRef, qualified_join ) ,
        "OSQLParseTreeIterator::getQualified_join: illegal node!" );

    aTableRange.clear();

    const OSQLParseNode* pNode = getTableNode(_rTables,pTableRef->getChild(0),aTableRange);
    if ( isTableNode( pNode ) )
        traverseOneTableName( _rTables, pNode, aTableRange );

    sal_uInt32 nPos = 4;
    if( SQL_ISRULE(pTableRef,cross_union) || pTableRef->getChild(1)->getTokenID() != SQL_TOKEN_NATURAL)
    {
        nPos = 3;
        // join_condition,named_columns_join
        if ( SQL_ISRULE( pTableRef, qualified_join ) )
        {
            const OSQLParseNode* pJoin_spec = pTableRef->getChild(4);
            if ( SQL_ISRULE( pJoin_spec, join_condition ) )
            {
                impl_fillJoinConditions(pJoin_spec->getChild(1));
            }
            else
            {
                const OSQLParseNode* pColumnCommalist = pJoin_spec->getChild(2);
                // All columns in the column_commalist ...
                for (size_t i = 0; i < pColumnCommalist->count(); i++)
                {
                    const OSQLParseNode * pCol = pColumnCommalist->getChild(i);
                    // add twice because the column must exists in both tables
                    m_pImpl->m_aJoinConditions.emplace_back(pCol,pCol);
                }
            }
        }
    }

    pNode = getTableNode(_rTables,pTableRef->getChild(nPos),aTableRange);
    if ( isTableNode( pNode ) )
        traverseOneTableName( _rTables, pNode, aTableRange );
}

const OSQLParseNode* OSQLParseTreeIterator::getTableNode( OSQLTables& _rTables, const OSQLParseNode *pTableRef,OUString& rTableRange )
{
    OSL_PRECOND( SQL_ISRULE( pTableRef, table_ref ) || SQL_ISRULE( pTableRef, joined_table )
              || SQL_ISRULE( pTableRef, qualified_join ) || SQL_ISRULE( pTableRef, cross_union ),
        "OSQLParseTreeIterator::getTableNode: only to be called for table_ref nodes!" );

    const OSQLParseNode* pTableNameNode = nullptr;

    if ( SQL_ISRULE( pTableRef, joined_table ) )
    {
        getQualified_join( _rTables, pTableRef->getChild(1), rTableRange );
    }
    if ( SQL_ISRULE( pTableRef, qualified_join ) || SQL_ISRULE( pTableRef, cross_union ) )
    {
        getQualified_join( _rTables, pTableRef, rTableRange );
    }
    else
    {
        rTableRange = OSQLParseNode::getTableRange(pTableRef);
        if  (   ( pTableRef->count() == 4 ) // '{' SQL_TOKEN_OJ joined_table '}'
            ||  ( pTableRef->count() == 5 ) // '(' joined_table ')' range_variable op_column_commalist
            )
        {
            getQualified_join( _rTables, pTableRef->getChild(6 - pTableRef->count()), rTableRange );
        }
        else if ( pTableRef->count() == 3 ) // subquery range_variable op_column_commalist || '(' joined_table ')'
        {
            const OSQLParseNode* pSubQuery = pTableRef->getChild(0);
            if ( pSubQuery->isToken() )
            {
                getQualified_join( _rTables, pTableRef->getChild(1), rTableRange );
            }
            else
            {
                OSL_ENSURE( pSubQuery->count() == 3, "sub queries should have 3 children!" );
                const OSQLParseNode* pQueryExpression = pSubQuery->getChild(1);
                if ( SQL_ISRULE( pQueryExpression, select_statement ) )
                {
                    getSelect_statement( *m_pImpl->m_pSubTables, pQueryExpression );
                    // TODO: now, we need to setup an OSQLTable from pQueryExpression in some way
                    //       and stick it in _rTables[rTableRange]. Probably fake it by
                    //       setting up a full OSQLParseTreeIterator on pQueryExpression
                    //       and using its m_aSelectColumns
                    //       This is necessary in stuff like "SELECT * FROM tbl1 INNER JOIN (SELECT foo, bar FROM tbl2) AS tbl3"
                    //       so that setSelectColumnName() can expand the "*" correctly.
                    //       See e.g. R_UserAndLastSubscription query of https://bugs.libreoffice.org/attachment.cgi?id=71871
                }
                else
                {
                    SAL_WARN( "connectivity.parse", "OSQLParseTreeIterator::getTableNode: subquery which is no select_statement: not yet implemented!" );
                }
            }
        }
        else if ( pTableRef->count() == 2 ) // table_node table_primary_as_range_column
        {
            pTableNameNode = pTableRef->getChild(0);
        }
        else
            SAL_WARN( "connectivity.parse", "OSQLParseTreeIterator::getTableNode: unhandled case!" );
    }

    return pTableNameNode;
}

void OSQLParseTreeIterator::getSelect_statement(OSQLTables& _rTables,const OSQLParseNode* pSelect)
{
    if(SQL_ISRULE(pSelect,union_statement))
    {
        getSelect_statement(_rTables,pSelect->getChild(0));
        //getSelect_statement(pSelect->getChild(3));
        return;
    }
    OSQLParseNode * pTableRefCommalist = pSelect->getChild(3)->getChild(0)->getChild(1);

    assert(pTableRefCommalist != nullptr && "OSQLParseTreeIterator: error in parse tree!");
    OSL_ENSURE(SQL_ISRULE(pTableRefCommalist,table_ref_commalist),"OSQLParseTreeIterator: error in parse tree!");

    const OSQLParseNode* pTableName = nullptr;
    OUString aTableRange;
    for (size_t i = 0; i < pTableRefCommalist->count(); i++)
    {   // Process FROM clause
        aTableRange.clear();

        const OSQLParseNode* pTableListElement = pTableRefCommalist->getChild(i);
        if ( isTableNode( pTableListElement ) )
        {
            traverseOneTableName( _rTables, pTableListElement, aTableRange );
        }
        else if ( SQL_ISRULE( pTableListElement, table_ref ) )
        {
            // Table references can be made up of table names, table names (+),'('joined_table')'(+)
            pTableName = pTableListElement->getChild(0);
            if( isTableNode( pTableName ) )
            {   // Found table names
                aTableRange = OSQLParseNode::getTableRange(pTableListElement);
                traverseOneTableName( _rTables, pTableName, aTableRange );
            }
            else if(SQL_ISPUNCTUATION(pTableName,"{"))
            {   // '{' SQL_TOKEN_OJ joined_table '}'
                getQualified_join( _rTables, pTableListElement->getChild(2), aTableRange );
            }
            else
            {   // '(' joined_table ')' range_variable op_column_commalist
                getTableNode( _rTables, pTableListElement, aTableRange );
            }
        }
        else if (SQL_ISRULE( pTableListElement, qualified_join ) || SQL_ISRULE( pTableListElement, cross_union ) )
        {
            getQualified_join( _rTables, pTableListElement, aTableRange );
        }
        else if ( SQL_ISRULE( pTableListElement, joined_table ) )
        {
            getQualified_join( _rTables, pTableListElement->getChild(1), aTableRange );
        }

        //  if (! aIteratorStatus.IsSuccessful()) break;
    }
}

bool OSQLParseTreeIterator::traverseTableNames(OSQLTables& _rTables)
{
    if ( m_pParseTree == nullptr )
        return false;

    OSQLParseNode* pTableName = nullptr;

    switch ( m_eStatementType )
    {
        case OSQLStatementType::Select:
            getSelect_statement( _rTables, m_pParseTree );
            break;

        case OSQLStatementType::CreateTable:
        case OSQLStatementType::Insert:
        case OSQLStatementType::Delete:
            pTableName = m_pParseTree->getChild(2);
            break;

        case OSQLStatementType::Update:
            pTableName = m_pParseTree->getChild(1);
            break;
        default:
            break;
    }

    if ( pTableName )
    {
        traverseOneTableName( _rTables, pTableName, OUString() );
    }

    return !hasErrors();
}

OUString OSQLParseTreeIterator::getColumnAlias(const OSQLParseNode* _pDerivedColumn)
{
    OSL_ENSURE(SQL_ISRULE(_pDerivedColumn,derived_column),"No derived column!");
    OUString sColumnAlias;
    if(_pDerivedColumn->getChild(1)->count() == 2)
        sColumnAlias = _pDerivedColumn->getChild(1)->getChild(1)->getTokenValue();
    else if(!_pDerivedColumn->getChild(1)->isRule())
        sColumnAlias = _pDerivedColumn->getChild(1)->getTokenValue();
    return sColumnAlias;
}


namespace
{
    void lcl_getColumnRange( const OSQLParseNode* _pColumnRef, const Reference< XConnection >& _rxConnection,
        OUString& _out_rColumnName, OUString& _out_rTableRange,
        const OSQLColumns* _pSelectColumns, OUString& _out_rColumnAliasIfPresent )
    {
        _out_rColumnName.clear();
        _out_rTableRange.clear();
        _out_rColumnAliasIfPresent.clear();
        if ( SQL_ISRULE( _pColumnRef, column_ref ) )
        {
            if( _pColumnRef->count() > 1 )
            {
                for ( sal_Int32 i=0; i<static_cast<sal_Int32>(_pColumnRef->count())-2; ++i )
                    _pColumnRef->getChild(i)->parseNodeToStr( _out_rTableRange, _rxConnection, nullptr, false, false );
                _out_rColumnName = _pColumnRef->getChild( _pColumnRef->count()-1 )->getChild(0)->getTokenValue();
            }
            else
                _out_rColumnName = _pColumnRef->getChild(0)->getTokenValue();

            // look up the column in the select column, to find a possible alias
            if ( _pSelectColumns )
            {
                for (const Reference< XPropertySet >& xColumn : *_pSelectColumns)
                {
                    try
                    {
                        OUString sName, sTableName;
                        xColumn->getPropertyValue( OMetaConnection::getPropMap().getNameByIndex( PROPERTY_ID_REALNAME ) ) >>= sName;
                        xColumn->getPropertyValue( OMetaConnection::getPropMap().getNameByIndex( PROPERTY_ID_TABLENAME ) ) >>= sTableName;
                        if ( sName == _out_rColumnName && ( _out_rTableRange.isEmpty() || sTableName == _out_rTableRange ) )
                        {
                            xColumn->getPropertyValue( OMetaConnection::getPropMap().getNameByIndex( PROPERTY_ID_NAME ) ) >>= _out_rColumnAliasIfPresent;
                            break;
                        }
                    }
                    catch( const Exception& )
                    {
                        DBG_UNHANDLED_EXCEPTION("connectivity.parse");
                    }
                }
            }
        }
        else if(SQL_ISRULE(_pColumnRef,general_set_fct) || SQL_ISRULE(_pColumnRef,set_fct_spec))
        { // Function
            _pColumnRef->parseNodeToStr( _out_rColumnName, _rxConnection );
        }
        else  if(_pColumnRef->getNodeType() == SQLNodeType::Name)
            _out_rColumnName = _pColumnRef->getTokenValue();
    }
}


void OSQLParseTreeIterator::getColumnRange( const OSQLParseNode* _pColumnRef,
                        OUString& _rColumnName,
                        OUString& _rTableRange) const
{
    OUString sDummy;
    lcl_getColumnRange( _pColumnRef, m_pImpl->m_xConnection, _rColumnName, _rTableRange, nullptr, sDummy );
}


void OSQLParseTreeIterator::getColumnRange( const OSQLParseNode* _pColumnRef,
                        OUString& _rColumnName,
                        OUString& _rTableRange,
                        OUString& _out_rColumnAliasIfPresent ) const
{
    lcl_getColumnRange( _pColumnRef, m_pImpl->m_xConnection, _rColumnName, _rTableRange, &*m_aSelectColumns, _out_rColumnAliasIfPresent );
}


void OSQLParseTreeIterator::getColumnRange( const OSQLParseNode* _pColumnRef,
    const Reference< XConnection >& _rxConnection, OUString& _out_rColumnName, OUString& _out_rTableRange )
{
    OUString sDummy;
    lcl_getColumnRange( _pColumnRef, _rxConnection, _out_rColumnName, _out_rTableRange, nullptr, sDummy );
}


void OSQLParseTreeIterator::traverseCreateColumns(const OSQLParseNode* pSelectNode)
{
    //  aIteratorStatus.Clear();

    if (!pSelectNode || m_eStatementType != OSQLStatementType::CreateTable || m_pImpl->m_pTables->empty())
    {
        impl_appendError( IParseContext::ErrorCode::General );
        return;
    }
    if (!SQL_ISRULE(pSelectNode,base_table_element_commalist))
        return ;

    for (size_t i = 0; i < pSelectNode->count(); i++)
    {
        OSQLParseNode *pColumnRef = pSelectNode->getChild(i);

        if (SQL_ISRULE(pColumnRef,column_def))
        {
            OUString aColumnName;
            OUString aTypeName;
            sal_Int32 nType = DataType::VARCHAR;
            aColumnName = pColumnRef->getChild(0)->getTokenValue();

            OSQLParseNode *pDatatype = pColumnRef->getChild(1);
            if (pDatatype && SQL_ISRULE(pDatatype,character_string_type))
            {
                const OSQLParseNode *pType = pDatatype->getChild(0);
                aTypeName = pType->getTokenValue();
                if (pDatatype->count() == 2 && (pType->getTokenID() == SQL_TOKEN_CHAR || pType->getTokenID() == SQL_TOKEN_CHARACTER ))
                    nType = DataType::CHAR;
            }
            else if(pDatatype && pDatatype->getNodeType() == SQLNodeType::Keyword)
            {
                aTypeName = "VARCHAR";
            }

            if (!aTypeName.isEmpty())
            {
                //TODO:Create a new class for create statement to handle field length
                rtl::Reference<OParseColumn> pColumn = new OParseColumn(aColumnName,aTypeName,OUString(),OUString(),
                    ColumnValue::NULLABLE_UNKNOWN,0,0,nType,false,false,isCaseSensitive(),
                    OUString(),OUString(),OUString());
                pColumn->setFunction(false);
                pColumn->setRealName(aColumnName);

                m_aCreateColumns->push_back(pColumn);
            }
        }

    }
}

bool OSQLParseTreeIterator::traverseSelectColumnNames(const OSQLParseNode* pSelectNode)
{
    if ( !( m_pImpl->m_nIncludeMask & TraversalParts::SelectColumns ) )
        return true;

    if (!pSelectNode || m_eStatementType != OSQLStatementType::Select || m_pImpl->m_pTables->empty())
    {
        impl_appendError( IParseContext::ErrorCode::General );
        return false;
    }

    if(SQL_ISRULE(pSelectNode,union_statement))
    {
        return  traverseSelectColumnNames( pSelectNode->getChild( 0 ) )
            /*&&  traverseSelectColumnNames( pSelectNode->getChild( 3 ) )*/;
    }

    // nyi: more checks for correct structure!
    if (pSelectNode->getChild(2)->isRule() && SQL_ISPUNCTUATION(pSelectNode->getChild(2)->getChild(0),"*"))
    {
        // SELECT * ...
        setSelectColumnName(u"*"_ustr, u""_ustr, u""_ustr);
    }
    else if (SQL_ISRULE(pSelectNode->getChild(2),scalar_exp_commalist))
    {
        // SELECT column[,column] or SELECT COUNT(*) ...
        OSQLParseNode * pSelection = pSelectNode->getChild(2);

        for (size_t i = 0; i < pSelection->count(); i++)
        {
            OSQLParseNode *pColumnRef = pSelection->getChild(i);

            //if (SQL_ISRULE(pColumnRef,select_sublist))
            if (SQL_ISRULE(pColumnRef,derived_column) &&
                SQL_ISRULE(pColumnRef->getChild(0),column_ref) &&
                pColumnRef->getChild(0)->count() == 3 &&
                SQL_ISPUNCTUATION(pColumnRef->getChild(0)->getChild(2),"*"))
            {
                // All the table's columns
                OUString aTableRange;
                pColumnRef->getChild(0)->parseNodeToStr( aTableRange, m_pImpl->m_xConnection, nullptr, false, false );
                setSelectColumnName(u"*"_ustr, u""_ustr, aTableRange);
                continue;
            }
            else if (SQL_ISRULE(pColumnRef,derived_column))
            {
                OUString aColumnAlias(getColumnAlias(pColumnRef)); // can be empty
                OUString sColumnName;
                OUString aTableRange;
                sal_Int32 nType = DataType::VARCHAR;
                bool bFkt(false);
                pColumnRef = pColumnRef->getChild(0);
                while (
                        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))
                {
                    getColumnRange(pColumnRef,sColumnName,aTableRange);
                    OSL_ENSURE(!sColumnName.isEmpty(),"Column name must not be empty!");
                }
                else /*if (SQL_ISRULE(pColumnRef,general_set_fct) || SQL_ISRULE(pColumnRef,set_fct_spec)    ||
                         SQL_ISRULE(pColumnRef,position_exp)    || SQL_ISRULE(pColumnRef,extract_exp)   ||
                         SQL_ISRULE(pColumnRef,length_exp)      || SQL_ISRULE(pColumnRef,char_value_fct)||
                         SQL_ISRULE(pColumnRef,num_value_exp)   || SQL_ISRULE(pColumnRef,term))*/
                {
                    // Function call present
                    pColumnRef->parseNodeToStr( sColumnName, m_pImpl->m_xConnection );
                    // check if the column is also a parameter
                    traverseSearchCondition(pColumnRef); // num_value_exp

                    if ( pColumnRef->isRule() )
                    {
                        // FIXME: the if condition is not quite right
                        //        many expressions are rules, e.g. "5+3"
                        //        or even: "colName + 1"
                        bFkt = true;
                        nType = getFunctionReturnType(pColumnRef);
                    }
                }
                /*
                else
                {
                    aIteratorStatus.setStatementTooComplex();
                    return;
                }
                */
                if(aColumnAlias.isEmpty())
                    aColumnAlias = sColumnName;
                setSelectColumnName(sColumnName,aColumnAlias,aTableRange,bFkt,nType,SQL_ISRULE(pColumnRef,general_set_fct) || SQL_ISRULE(pColumnRef,set_fct_spec));
            }
        }
    }

    return !hasErrors();
}


bool OSQLParseTreeIterator::traverseOrderByColumnNames(const OSQLParseNode* pSelectNode)
{
    traverseByColumnNames( pSelectNode, true );
    return !hasErrors();
}

void OSQLParseTreeIterator::traverseByColumnNames(const OSQLParseNode* pSelectNode, bool _bOrder)
{
    //  aIteratorStatus.Clear();

    if (pSelectNode == nullptr)
    {
        //aIteratorStatus.setInvalidStatement();
        return;
    }

    if (m_eStatementType != OSQLStatementType::Select)
    {
        //aIteratorStatus.setInvalidStatement();
        return;
    }

    if(SQL_ISRULE(pSelectNode,union_statement))
    {
        traverseByColumnNames(pSelectNode->getChild(0),_bOrder);
        return;
    }

    OSL_ENSURE(pSelectNode->count() >= 4,"OSQLParseTreeIterator: error in parse tree!");

    OSQLParseNode * pTableExp = pSelectNode->getChild(3);
    assert(pTableExp != nullptr && "OSQLParseTreeIterator: error in parse tree!");
    OSL_ENSURE(SQL_ISRULE(pTableExp,table_exp),"OSQLParseTreeIterator:table_exp error in parse tree!");
    OSL_ENSURE(pTableExp->count() == TABLE_EXPRESSION_CHILD_COUNT,"OSQLParseTreeIterator: error in parse tree!");

    sal_uInt32 nPos = ( _bOrder ? ORDER_BY_CHILD_POS : 2 );

    OSQLParseNode * pOptByClause = pTableExp->getChild(nPos);
    assert(pOptByClause != nullptr && "OSQLParseTreeIterator: error in parse tree!");
    if ( pOptByClause->count() == 0 )
        return;

    OSL_ENSURE(pOptByClause->count() == 3,"OSQLParseTreeIterator: error in parse tree!");

    OSQLParseNode * pOrderingSpecCommalist = pOptByClause->getChild(2);
    assert(pOrderingSpecCommalist != nullptr && "OSQLParseTreeIterator: error in parse tree!");
    OSL_ENSURE(!_bOrder || SQL_ISRULE(pOrderingSpecCommalist,ordering_spec_commalist),"OSQLParseTreeIterator:ordering_spec_commalist error in parse tree!");
    OSL_ENSURE(pOrderingSpecCommalist->count() > 0,"OSQLParseTreeIterator: error in parse tree!");

    OUString sColumnName;
    OUString aTableRange;
    sal_uInt32 nCount = pOrderingSpecCommalist->count();
    for (sal_uInt32 i = 0; i < nCount; ++i)
    {
        OSQLParseNode* pColumnRef  = pOrderingSpecCommalist->getChild(i);
        assert(pColumnRef != nullptr && "OSQLParseTreeIterator: error in parse tree!");
        if ( _bOrder )
        {
            OSL_ENSURE(SQL_ISRULE(pColumnRef,ordering_spec),"OSQLParseTreeIterator:ordering_spec error in parse tree!");
            OSL_ENSURE(pColumnRef->count() == 2,"OSQLParseTreeIterator: error in parse tree!");

            pColumnRef = pColumnRef->getChild(0);
        }
        assert(pColumnRef != nullptr && "OSQLParseTreeIterator: error in parse tree!");
        aTableRange.clear();
        sColumnName.clear();
        if ( SQL_ISRULE(pColumnRef,column_ref) )
        {
            // Column name (and TableRange):
            getColumnRange(pColumnRef,sColumnName,aTableRange);
        }
        else
        {   // here I found a predicate
            pColumnRef->parseNodeToStr( sColumnName, m_pImpl->m_xConnection, nullptr, false, false );
        }
        OSL_ENSURE(!sColumnName.isEmpty(),"sColumnName must not be empty!");
        if ( _bOrder )
        {
            // Ascending/Descending
            OSQLParseNode * pOptAscDesc = pColumnRef->getParent()->getChild(1);
            OSL_ENSURE(pOptAscDesc != nullptr,"OSQLParseTreeIterator: error in parse tree!");

            bool bAscending = ! (pOptAscDesc && SQL_ISTOKEN(pOptAscDesc,DESC));
            setOrderByColumnName(sColumnName, aTableRange,bAscending);
        }
        else
            setGroupByColumnName(sColumnName, aTableRange);
    }
}

bool OSQLParseTreeIterator::traverseGroupByColumnNames(const OSQLParseNode* pSelectNode)
{
    traverseByColumnNames( pSelectNode, false );
    return !hasErrors();
}


namespace
{
    OUString lcl_generateParameterName( const OSQLParseNode& _rParentNode, const OSQLParseNode& _rParamNode )
    {
        OUString sColumnName(  u"param"_ustr  );
        const sal_Int32 nCount = static_cast<sal_Int32>(_rParentNode.count());
        for ( sal_Int32 i = 0; i < nCount; ++i )
        {
            if ( _rParentNode.getChild(i) == &_rParamNode )
            {
                sColumnName += OUString::number( i+1 );
                break;
            }
        }
        return sColumnName;
    }
}


void OSQLParseTreeIterator::traverseParameters(const OSQLParseNode* _pNode)
{
    if ( _pNode == nullptr )
        return;

    OUString sColumnName, sTableRange, aColumnAlias;
    const OSQLParseNode* pParent = _pNode->getParent();
    if ( pParent != nullptr )
    {
        if ( SQL_ISRULE(pParent,comparison_predicate) ) // x = X
        {
            sal_uInt32 nPos = 0;
            if ( pParent->getChild(nPos) == _pNode )
                nPos = 2;
            const OSQLParseNode* pOther = pParent->getChild(nPos);
            if ( SQL_ISRULE( pOther, column_ref ) )
                getColumnRange( pOther, sColumnName, sTableRange, aColumnAlias);
            else
                pOther->parseNodeToStr( sColumnName, m_pImpl->m_xConnection, nullptr, false, false );
        } // if ( SQL_ISRULE(pParent,comparison_predicate) ) // x = X
        else if ( SQL_ISRULE(pParent,other_like_predicate_part_2) )
        {
            const OSQLParseNode* pOther = pParent->getParent()->getChild(0);
            if ( SQL_ISRULE( pOther, column_ref ) )
                getColumnRange( pOther, sColumnName, sTableRange, aColumnAlias);
            else
                pOther->parseNodeToStr( sColumnName, m_pImpl->m_xConnection, nullptr, false, false );
        }
        else if ( SQL_ISRULE(pParent,between_predicate_part_2) )
        {
            const OSQLParseNode* pOther = pParent->getParent()->getChild(0);
            if ( SQL_ISRULE( pOther, column_ref ) )
                getColumnRange( pOther, sColumnName, sTableRange, aColumnAlias);
            else
            {
                pOther->parseNodeToStr( sColumnName, m_pImpl->m_xConnection, nullptr, false, false );
                lcl_generateParameterName( *pParent, *_pNode );
            }
        }
        else if ( pParent->getNodeType() == SQLNodeType::CommaListRule )
        {
            lcl_generateParameterName( *pParent, *_pNode );
        }
    }
    traverseParameter( _pNode, pParent, sColumnName, sTableRange, aColumnAlias );
    const sal_uInt32 nCount = _pNode->count();
    for (sal_uInt32 i = 0; i < nCount; ++i)
    {
        const OSQLParseNode* pChild  = _pNode->getChild(i);
        traverseParameters( pChild );
    }
}

bool OSQLParseTreeIterator::traverseSelectionCriteria(const OSQLParseNode* pSelectNode)
{
    if ( pSelectNode == nullptr )
        return false;


    // Analyse parse tree (depending on statement type)
    // and set pointer to WHERE clause:
    OSQLParseNode * pWhereClause = nullptr;

    if (m_eStatementType == OSQLStatementType::Select)
    {
        if(SQL_ISRULE(pSelectNode,union_statement))
        {
            return  traverseSelectionCriteria( pSelectNode->getChild( 0 ) )
                &&  traverseSelectionCriteria( pSelectNode->getChild( 3 ) );
        }
        OSL_ENSURE(pSelectNode->count() >= 4,"OSQLParseTreeIterator: error in parse tree!");

        OSQLParseNode * pTableExp = pSelectNode->getChild(3);
        assert(pTableExp != nullptr && "OSQLParseTreeIterator: error in parse tree!");
        OSL_ENSURE(SQL_ISRULE(pTableExp,table_exp),"OSQLParseTreeIterator: error in parse tree!");
        OSL_ENSURE(pTableExp->count() == TABLE_EXPRESSION_CHILD_COUNT,"OSQLParseTreeIterator: error in parse tree!");

        pWhereClause = pTableExp->getChild(1);
    } else if (SQL_ISRULE(pSelectNode,update_statement_searched)) {
        OSL_ENSURE(pSelectNode->count() == 5,"OSQLParseTreeIterator: error in parse tree!");
        pWhereClause = pSelectNode->getChild(4);
    } else if (SQL_ISRULE(pSelectNode,delete_statement_searched)) {
        OSL_ENSURE(pSelectNode->count() == 4,"OSQLParseTreeIterator: error in parse tree!");
        pWhereClause = pSelectNode->getChild(3);
    } else if (SQL_ISRULE(pSelectNode,delete_statement_positioned)) {
        // nyi
        SAL_WARN( "connectivity.parse","OSQLParseTreeIterator::getSelectionCriteria: positioned nyi");
    } else {
        // Other statement, no selection criteria
        return false;
    }

    if (!pWhereClause || !SQL_ISRULE(pWhereClause,where_clause))
    {
        // The WHERE clause is optional most of the time; which means it could be a "optional_where_clause".
        OSL_ENSURE(pWhereClause && SQL_ISRULE(pWhereClause,opt_where_clause),"OSQLParseTreeIterator: error in parse tree!");
        return false;
    }

    // But if it's a where_clause, then it must not be empty
    OSL_ENSURE(pWhereClause->count() == 2,"OSQLParseTreeIterator: error in parse tree!");

    OSQLParseNode * pComparisonPredicate = pWhereClause->getChild(1);
    OSL_ENSURE(pComparisonPredicate != nullptr,"OSQLParseTreeIterator: error in parse tree!");


    // Process the comparison criteria now


    traverseSearchCondition(pComparisonPredicate);

    return !hasErrors();
}


void OSQLParseTreeIterator::traverseSearchCondition(OSQLParseNode const * pSearchCondition)
{
    if (
            SQL_ISRULE(pSearchCondition,boolean_primary) &&
            pSearchCondition->count() == 3 &&
            SQL_ISPUNCTUATION(pSearchCondition->getChild(0),"(") &&
            SQL_ISPUNCTUATION(pSearchCondition->getChild(2),")")
        )
    {
        // Round brackets
        traverseSearchCondition(pSearchCondition->getChild(1));
    }
    // The first element is an OR logical operation
    else  if ( SQL_ISRULE(pSearchCondition,search_condition) && pSearchCondition->count() == 3 )
    {
        // if this assert fails, the SQL grammar has changed!
        assert(SQL_ISTOKEN(pSearchCondition->getChild(1),OR));
        // Then process recursively (use the same row) ...
        traverseSearchCondition(pSearchCondition->getChild(0));
//      if (! aIteratorStatus.IsSuccessful())
//          return;

        // Continue with the right child
        traverseSearchCondition(pSearchCondition->getChild(2));
    }
    // The first element is an AND logical operation (again)
    else if ( SQL_ISRULE(pSearchCondition,boolean_term) && pSearchCondition->count() == 3 )
    {
        // Then process recursively (use the same row)
        traverseSearchCondition(pSearchCondition->getChild(0));
//      if (! aIteratorStatus.IsSuccessful())
//          return;

        // Continue with the right child
        traverseSearchCondition(pSearchCondition->getChild(2));
    }
    // Else, process single search criteria (like =, !=, ..., LIKE, IS NULL etc.)
    else if (SQL_ISRULE(pSearchCondition,comparison_predicate) )
    {
        OUString aValue;
        pSearchCondition->getChild(2)->parseNodeToStr( aValue, m_pImpl->m_xConnection, nullptr, false, false );
        traverseOnePredicate(pSearchCondition->getChild(0),aValue,pSearchCondition->getChild(2));
        impl_fillJoinConditions(pSearchCondition);
//      if (! aIteratorStatus.IsSuccessful())
//          return;
    }
    else if (SQL_ISRULE(pSearchCondition,like_predicate) /*&& SQL_ISRULE(pSearchCondition->getChild(0),column_ref)*/)
    {
        OSL_ENSURE(pSearchCondition->count() == 2,"OSQLParseTreeIterator: error in parse tree!");
        const OSQLParseNode* pPart2 = pSearchCondition->getChild(1);

        sal_Int32 nCurrentPos = pPart2->count()-2;

        OSQLParseNode * pNum_value_exp  = pPart2->getChild(nCurrentPos);
        OSQLParseNode * pOptEscape      = pPart2->getChild(nCurrentPos+1);

        assert(pNum_value_exp != nullptr && "OSQLParseTreeIterator: error in parse tree!");
        assert(pOptEscape != nullptr && "OSQLParseTreeIterator: error in parse tree!");

        if (pOptEscape->count() != 0)
        {
            //  aIteratorStatus.setStatementTooComplex();
            return;
        }

        OUString aValue;
        OSQLParseNode * pParam = nullptr;
        if (SQL_ISRULE(pNum_value_exp,parameter))
            pParam = pNum_value_exp;
        else if(pNum_value_exp->isToken())
            // Normal value
            aValue = pNum_value_exp->getTokenValue();
        else
        {
            pNum_value_exp->parseNodeToStr( aValue, m_pImpl->m_xConnection, nullptr, false, false );
            pParam = pNum_value_exp;
        }

        traverseOnePredicate(pSearchCondition->getChild(0),aValue,pParam);
//      if (! aIteratorStatus.IsSuccessful())
//          return;
    }
    else if (SQL_ISRULE(pSearchCondition,in_predicate))
    {
        OSL_ENSURE(pSearchCondition->count() == 2,"OSQLParseTreeIterator: error in parse tree!");
        const OSQLParseNode* pPart2 = pSearchCondition->getChild(1);

        traverseSearchCondition(pSearchCondition->getChild(0));
        //  if (! aIteratorStatus.IsSuccessful()) return;

        OSQLParseNode* pChild = pPart2->getChild(2);
        if ( SQL_ISRULE(pChild->getChild(0),subquery) )
        {
            traverseTableNames( *m_pImpl->m_pSubTables );
            traverseSelectionCriteria(pChild->getChild(0)->getChild(1));
        }
        else
        { // '(' value_exp_commalist ')'
            pChild = pChild->getChild(1);
            sal_Int32 nCount = pChild->count();
            for (sal_Int32 i=0; i < nCount; ++i)
            {
                traverseSearchCondition(pChild->getChild(i));
            }
        }
    }
    else if (SQL_ISRULE(pSearchCondition,test_for_null) /*&& SQL_ISRULE(pSearchCondition->getChild(0),column_ref)*/)
    {
        OSL_ENSURE(pSearchCondition->count() == 2,"OSQLParseTreeIterator: error in parse tree!");
        const OSQLParseNode* pPart2 = pSearchCondition->getChild(1);
        OSL_ENSURE(SQL_ISTOKEN(pPart2->getChild(0),IS),"OSQLParseTreeIterator: error in parse tree!");

        OUString aString;
        traverseOnePredicate(pSearchCondition->getChild(0),aString,nullptr);
        //  if (! aIteratorStatus.IsSuccessful()) return;
    }
    else if (SQL_ISRULE(pSearchCondition,num_value_exp) || SQL_ISRULE(pSearchCondition,term))
    {
        OUString aString;
        traverseOnePredicate(pSearchCondition->getChild(0),aString,pSearchCondition->getChild(0));
        traverseOnePredicate(pSearchCondition->getChild(2),aString,pSearchCondition->getChild(2));
    }
    // Just pass on the error
}

void OSQLParseTreeIterator::traverseParameter(const OSQLParseNode* _pParseNode
                                              ,const OSQLParseNode* _pParentNode
                                              ,const OUString& _aColumnName
                                              ,OUString& _aTableRange
                                              ,const OUString& _rColumnAlias)
{
    if ( !SQL_ISRULE( _pParseNode, parameter ) )
        return;

    if ( !( m_pImpl->m_nIncludeMask & TraversalParts::Parameters ) )
        // parameters not to be included in the traversal
        return;

    OSL_ENSURE(_pParseNode->count() > 0,"OSQLParseTreeIterator: error in parse tree!");
    OSQLParseNode * pMark = _pParseNode->getChild(0);
    OUString sParameterName;

    if (SQL_ISPUNCTUATION(pMark,"?"))
    {
        sParameterName =    !_rColumnAlias.isEmpty()
                        ?   _rColumnAlias
                        :   !_aColumnName.isEmpty()
                        ?   _aColumnName
                        :   u"?"_ustr;
    }
    else if (SQL_ISPUNCTUATION(pMark,":"))
    {
        sParameterName = _pParseNode->getChild(1)->getTokenValue();
    }
    else if (SQL_ISPUNCTUATION(pMark,"["))
    {
        sParameterName = _pParseNode->getChild(1)->getTokenValue();
    }
    else
    {
        SAL_WARN( "connectivity.parse","OSQLParseTreeIterator: error in parse tree!");
    }

    // found a parameter
    if ( _pParentNode && (SQL_ISRULE(_pParentNode,general_set_fct) || SQL_ISRULE(_pParentNode,set_fct_spec)) )
    {// found a function as column_ref
        OUString sFunctionName;
        _pParentNode->getChild(0)->parseNodeToStr( sFunctionName, m_pImpl->m_xConnection, nullptr, false, false );
        const sal_uInt32 nCount = _pParentNode->count();
        sal_uInt32 i = 0;
        for(; i < nCount;++i)
        {
            if ( _pParentNode->getChild(i) == _pParseNode )
                break;
        }
        sal_Int32 nType = ::connectivity::OSQLParser::getFunctionParameterType( _pParentNode->getChild(0)->getTokenID(), i-1);

        rtl::Reference<OParseColumn> pColumn = new OParseColumn(   sParameterName,
                                                    OUString(),
                                                    OUString(),
                                                    OUString(),
                                                    ColumnValue::NULLABLE_UNKNOWN,
                                                    0,
                                                    0,
                                                    nType,
                                                    false,
                                                    false,
                                                    isCaseSensitive(),
                                                    OUString(),
                                                    OUString(),
                                                    OUString());
        pColumn->setFunction(true);
        pColumn->setAggregateFunction(true);
        pColumn->setRealName(sFunctionName);
        m_aParameters->push_back(pColumn);
    }
    else
    {
        bool bNotFound = true;
        OSQLColumns::const_iterator aIter = ::connectivity::find(
            m_aSelectColumns->begin(),
            m_aSelectColumns->end(),
            _aColumnName,::comphelper::UStringMixEqual( isCaseSensitive() )
        );
        if(aIter != m_aSelectColumns->end())
        {
            rtl::Reference<OParseColumn> pNewColumn = new OParseColumn(*aIter,isCaseSensitive());
            pNewColumn->setName(sParameterName);
            pNewColumn->setRealName(_aColumnName);
            m_aParameters->push_back(pNewColumn);
            bNotFound = false;
        }
        else if(!_aColumnName.isEmpty())// search in the tables for the right one
        {

            Reference<XPropertySet> xColumn = findColumn( _aColumnName, _aTableRange, true );

            if ( xColumn.is() )
            {
                rtl::Reference<OParseColumn> pNewColumn = new OParseColumn(xColumn,isCaseSensitive());
                pNewColumn->setName(sParameterName);
                pNewColumn->setRealName(_aColumnName);
                m_aParameters->push_back(pNewColumn);
                bNotFound = false;
            }
        }
        if ( bNotFound )
        {
            sal_Int32 nType = DataType::VARCHAR;
            OSQLParseNode* pParent = _pParentNode ? _pParentNode->getParent() : nullptr;
            if ( pParent && (SQL_ISRULE(pParent,general_set_fct) || SQL_ISRULE(pParent,set_fct_spec)) )
            {
                const sal_uInt32 nCount = _pParentNode->count();
                sal_uInt32 i = 0;
                for(; i < nCount;++i)
                {
                    if ( _pParentNode->getChild(i) == _pParseNode )
                        break;
                }
                nType = ::connectivity::OSQLParser::getFunctionParameterType( pParent->getChild(0)->getTokenID(), i+1);
            }

            OUString aNewColName(getUniqueColumnName(getSelectColumnNames(), sParameterName));

            rtl::Reference<OParseColumn> pColumn = new OParseColumn(aNewColName,
                                                    OUString(),
                                                    OUString(),
                                                    OUString(),
                                                    ColumnValue::NULLABLE_UNKNOWN,
                                                    0,
                                                    0,
                                                    nType,
                                                    false,
                                                    false,
                                                    isCaseSensitive(),
                                                    OUString(),
                                                    OUString(),
                                                    OUString());
            pColumn->setName(aNewColName);
            pColumn->setRealName(sParameterName);
            m_aParameters->push_back(pColumn);
        }
    }
}

void OSQLParseTreeIterator::traverseOnePredicate(
                                OSQLParseNode const * pColumnRef,
                                OUString& rValue,
                                OSQLParseNode const * pParseNode)
{
    if ( !pParseNode )
        return;

    // Column name (and TableRange):
    OUString aColumnName, aTableRange, sColumnAlias;
    getColumnRange( pColumnRef, aColumnName, aTableRange, sColumnAlias);

    OUString aName;

    /*if (SQL_ISRULE(pParseNode,parameter))
        traverseParameter( pParseNode, pColumnRef, aColumnName, aTableRange, sColumnAlias );
    else */if (SQL_ISRULE(pParseNode,column_ref))// Column-Name (and TableRange):
        getColumnRange(pParseNode,aName,rValue);
    else
    {
        traverseSearchCondition(pParseNode);
        //  if (! aIteratorStatus.IsSuccessful()) return;
    }
}


void OSQLParseTreeIterator::traverseAll()
{
    impl_traverse( TraversalParts::All );
}


void OSQLParseTreeIterator::impl_traverse( TraversalParts _nIncludeMask )
{
    // resets our errors
    m_xErrors.reset();

    m_pImpl->m_nIncludeMask = _nIncludeMask;

    if ( !traverseTableNames( *m_pImpl->m_pTables ) )
        return;

    switch ( m_eStatementType )
    {
    case OSQLStatementType::Select:
    {
        const OSQLParseNode* pSelectNode = m_pParseTree;
        traverseParameters( pSelectNode );
        if  (   !traverseSelectColumnNames( pSelectNode )
            ||  !traverseOrderByColumnNames( pSelectNode )
            ||  !traverseGroupByColumnNames( pSelectNode )
            ||  !traverseSelectionCriteria( pSelectNode )
            )
            return;
    }
    break;
    case OSQLStatementType::CreateTable:
    {
        //0     |  1  |  2   |3|        4         |5
        //create table sc.foo ( a char(20), b char )
        const OSQLParseNode* pCreateNode = m_pParseTree->getChild(4);
        traverseCreateColumns(pCreateNode);
    }
    break;
    case OSQLStatementType::Insert:
        break;
    default:
        break;
    }
}

// Dummy implementations


OSQLTable OSQLParseTreeIterator::impl_createTableObject( const OUString& rTableName,
    const OUString& rCatalogName, const OUString& rSchemaName )
{
    OSL_PRECOND( m_eStatementType == OSQLStatementType::CreateTable,
        "OSQLParseTreeIterator::impl_createTableObject: only to be called for CREATE TABLE statements!" );
        // (in all other cases, m_pTables is to contain the table objects as obtained from the tables
        // container of the connection (m_xTablesContainer)

    OSQLTable aReturnTable = new OTable(
        nullptr,
        false,
        rTableName,
        u"Table"_ustr,
        u"New Created Table"_ustr,
        rSchemaName,
        rCatalogName
    );
    return aReturnTable;
}

void OSQLParseTreeIterator::appendColumns(const OUString& _rTableAlias, const OSQLTable& _rTable)
{
    if (!_rTable.is())
        return;

    Reference<XNameAccess> xColumns = _rTable->getColumns();
    if ( !xColumns.is() )
        return;

    ::comphelper::UStringMixLess aCompare(isCaseSensitive());
    std::vector<OUString> aSelectColumnNames = getSelectColumnNames();

    for (auto& colName : xColumns->getElementNames())
    {
        OUString aName(getUniqueColumnName(aSelectColumnNames, colName));
        Reference< XPropertySet > xColumn;
        if(xColumns->hasByName(colName) && (xColumns->getByName(colName) >>= xColumn) && xColumn.is())
        {
            rtl::Reference<OParseColumn> pColumn = new OParseColumn(aName
                                                ,   getString(xColumn->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_TYPENAME)))
                                                ,   getString(xColumn->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_DEFAULTVALUE)))
                                                ,   getString(xColumn->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_DESCRIPTION)))
                                                ,   getINT32(xColumn->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_ISNULLABLE)))
                                                ,   getINT32(xColumn->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_PRECISION)))
                                                ,   getINT32(xColumn->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_SCALE)))
                                                ,   getINT32(xColumn->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_TYPE)))
                                                ,   getBOOL(xColumn->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_ISAUTOINCREMENT)))
                                                ,   getBOOL(xColumn->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_ISCURRENCY)))
                                                ,   isCaseSensitive()
                                                ,   getString(xColumn->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_CATALOGNAME)))
                                                ,   getString(xColumn->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_SCHEMANAME)))
                                                ,   getString(xColumn->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_TABLENAME))));

            pColumn->setTableName(_rTableAlias);
            pColumn->setRealName(colName);
            m_aSelectColumns->push_back(pColumn);
            // update aSelectColumnNames with newly insert aName
            aSelectColumnNames.insert(std::upper_bound(aSelectColumnNames.begin(), aSelectColumnNames.end(), aName, aCompare), aName);
        }
        else
            impl_appendError(IParseContext::ErrorCode::InvalidColumn, &colName, &_rTableAlias);
    }
}

void OSQLParseTreeIterator::setSelectColumnName(const OUString & rColumnName,const OUString & rColumnAlias, const OUString & rTableRange, bool bFkt, sal_Int32 _nType, bool bAggFkt)
{
    if(rColumnName.toChar() == '*' && rTableRange.isEmpty())
    {   // SELECT * ...
        for (auto const& table : *m_pImpl->m_pTables)
            appendColumns(table.first, table.second);
    }
    else if( rColumnName.toChar() == '*' && !rTableRange.isEmpty() )
    {   // SELECT <table>.*
        OSQLTables::const_iterator aFind = m_pImpl->m_pTables->find(rTableRange);

        if(aFind != m_pImpl->m_pTables->end())
            appendColumns(rTableRange, aFind->second);
    }
    else if ( rTableRange.isEmpty() )
    {   // SELECT <something> ...
        // without table specified
        if ( !bFkt )
        {
            rtl::Reference< OParseColumn> xNewColumn;

            for (auto const& table : *m_pImpl->m_pTables)
            {
                if ( !table.second.is() )
                    continue;

                Reference<XNameAccess> xColumns = table.second->getColumns();
                Reference< XPropertySet > xColumn;
                if  (   !xColumns->hasByName( rColumnName )
                    ||  !( xColumns->getByName( rColumnName ) >>= xColumn )
                    )
                    continue;

                OUString aNewColName(getUniqueColumnName(getSelectColumnNames(), rColumnAlias));

                rtl::Reference<OParseColumn> pColumn = new OParseColumn(xColumn,isCaseSensitive());
                xNewColumn = pColumn;
                pColumn->setTableName(table.first);
                pColumn->setName(aNewColName);
                pColumn->setRealName(rColumnName);

                break;
            }

            if ( !xNewColumn.is() )
            {
                // no function (due to the above !bFkt), no existing column
                // => assume an expression
                OUString aNewColName(getUniqueColumnName(getSelectColumnNames(), rColumnAlias));
                // did not find a column with this name in any of the tables
                rtl::Reference<OParseColumn> pColumn = new OParseColumn(
                    aNewColName,
                    u"VARCHAR"_ustr,
                        // TODO: does this match with _nType?
                        // Or should be fill this from the getTypeInfo of the connection?
                    OUString(),
                    OUString(),
                    ColumnValue::NULLABLE_UNKNOWN,
                    0,
                    0,
                    _nType,
                    false,
                    false,
                    isCaseSensitive(),
                    OUString(),
                    OUString(),
                    OUString()
                );

                xNewColumn = pColumn;
                pColumn->setRealName( rColumnName );
            }

            m_aSelectColumns->push_back( xNewColumn );
        }
        else
        {
            OUString aNewColName(getUniqueColumnName(getSelectColumnNames(), rColumnAlias));

            rtl::Reference<OParseColumn> pColumn = new OParseColumn(aNewColName,OUString(),OUString(),OUString(),
                ColumnValue::NULLABLE_UNKNOWN,0,0,_nType,false,false,isCaseSensitive(),
                OUString(),OUString(),OUString());
            pColumn->setFunction(true);
            pColumn->setAggregateFunction(bAggFkt);
            pColumn->setRealName(rColumnName);

            m_aSelectColumns->push_back(pColumn);
        }
    }
    else    // ColumnName and TableName exist
    {
        OSQLTables::const_iterator aFind = m_pImpl->m_pTables->find(rTableRange);

        bool bError = false;
        if (aFind != m_pImpl->m_pTables->end() && aFind->second.is())
        {
            if (bFkt)
            {
                OUString aNewColName(getUniqueColumnName(getSelectColumnNames(), rColumnAlias));

                rtl::Reference<OParseColumn> pColumn = new OParseColumn(aNewColName,OUString(),OUString(),OUString(),
                    ColumnValue::NULLABLE_UNKNOWN,0,0,_nType,false,false,isCaseSensitive(),
                    OUString(),OUString(),OUString());
                pColumn->setFunction(true);
                pColumn->setAggregateFunction(bAggFkt);
                pColumn->setRealName(rColumnName);
                SAL_WARN("connectivity.parse", "Trying to construct a column with Function==true and a TableName; this makes no sense.");
                assert(false);
                pColumn->setTableName(aFind->first);

                m_aSelectColumns->push_back(pColumn);
            }
            else
            {
                Reference< XPropertySet > xColumn;
                if (aFind->second->getColumns()->hasByName(rColumnName) && (aFind->second->getColumns()->getByName(rColumnName) >>= xColumn))
                {
                    OUString aNewColName(getUniqueColumnName(getSelectColumnNames(), rColumnAlias));

                    rtl::Reference<OParseColumn> pColumn = new OParseColumn(xColumn,isCaseSensitive());
                    pColumn->setName(aNewColName);
                    pColumn->setRealName(rColumnName);
                    pColumn->setTableName(aFind->first);

                    m_aSelectColumns->push_back(pColumn);
                }
                else
                    bError = true;
            }
        }
        else
            bError = true;

        // Table does not exist or lacking field
        if (bError)
        {
            OUString aNewColName(getUniqueColumnName(getSelectColumnNames(), rColumnAlias));

            rtl::Reference<OParseColumn> pColumn = new OParseColumn(aNewColName,OUString(),OUString(),OUString(),
                ColumnValue::NULLABLE_UNKNOWN,0,0,DataType::VARCHAR,false,false,isCaseSensitive(),
                OUString(),OUString(),OUString());
            pColumn->setFunction(true);
            pColumn->setAggregateFunction(bAggFkt);

            m_aSelectColumns->push_back(pColumn);
        }
    }
}

std::vector<OUString> OSQLParseTreeIterator::getSelectColumnNames() const
{
    ::comphelper::UStringMixLess aCompare(isCaseSensitive());

    std::vector<OUString> aColumnNames;
    OUString sPropertyName = OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_NAME);
    for (const auto& col : *m_aSelectColumns)
        aColumnNames.push_back(getString(col->getPropertyValue(sPropertyName)));
    std::sort(aColumnNames.begin(), aColumnNames.end(), aCompare);

    return aColumnNames;
}

OUString OSQLParseTreeIterator::getUniqueColumnName(const std::vector<OUString>& rColumnNames, const OUString& rColumnName) const
{
    ::comphelper::UStringMixLess aCompare(isCaseSensitive());
    if (!std::binary_search(rColumnNames.begin(), rColumnNames.end(), rColumnName, aCompare))
        return rColumnName;

    OUString aAlias;
    sal_Int32 i=1;
    do
    {
        aAlias = rColumnName + OUString::number(i++);
    }
    while (std::binary_search(rColumnNames.begin(), rColumnNames.end(), aAlias, aCompare));
    return aAlias;
}

void OSQLParseTreeIterator::setOrderByColumnName(const OUString & rColumnName, OUString & rTableRange, bool bAscending)
{
    Reference<XPropertySet> xColumn = findSelectColumn( rColumnName );
    if ( !xColumn.is() )
        xColumn = findColumn ( rColumnName, rTableRange, false );
    if ( xColumn.is() )
        m_aOrderColumns->push_back(new OOrderColumn( xColumn, rTableRange, isCaseSensitive(), bAscending ) );
    else
    {
        sal_Int32 nId = rColumnName.toInt32();
        if ( nId > 0 && o3tl::make_unsigned(nId) < m_aSelectColumns->size() )
            m_aOrderColumns->push_back( new OOrderColumn( (*m_aSelectColumns)[nId-1], isCaseSensitive(), bAscending ) );
    }

#ifdef SQL_TEST_PARSETREEITERATOR
    cout << "OSQLParseTreeIterator::setOrderByColumnName: "
         << (const char *) rColumnName << ", "
         << (const char *) rTableRange << ", "
         << (bAscending ? "true" : "false")
         << "\n";
#endif
}

void OSQLParseTreeIterator::setGroupByColumnName(const OUString & rColumnName, OUString & rTableRange)
{
    Reference<XPropertySet> xColumn = findColumn( rColumnName, rTableRange, false );
    if ( xColumn.is() )
        m_aGroupColumns->push_back(new OParseColumn(xColumn,isCaseSensitive()));
    else
    {
        sal_Int32 nId = rColumnName.toInt32();
        if ( nId > 0 && o3tl::make_unsigned(nId) < m_aSelectColumns->size() )
            m_aGroupColumns->push_back(new OParseColumn((*m_aSelectColumns)[nId-1],isCaseSensitive()));
    }

#ifdef SQL_TEST_PARSETREEITERATOR
    cout << "OSQLParseTreeIterator::setGroupByColumnName: "
         << (const char *) rColumnName << ", "
         << (const char *) rTableRange << ", "
         << (bAscending ? "true" : "false")
         << "\n";
#endif
}


const OSQLParseNode* OSQLParseTreeIterator::getWhereTree() const
{
    if (!m_pParseTree)
        return nullptr;

    // Analyse parse tree (depending on statement type)
    // and set pointer to WHERE clause:
    OSQLParseNode * pWhereClause = nullptr;
    if(getStatementType() == OSQLStatementType::Select)
    {
        OSL_ENSURE(m_pParseTree->count() >= 4,"ParseTreeIterator: error in parse tree!");
        OSQLParseNode * pTableExp = m_pParseTree->getChild(3);
        assert(pTableExp != nullptr && "OSQLParseTreeIterator: error in parse tree!");
        OSL_ENSURE(SQL_ISRULE(pTableExp,table_exp),"OSQLParseTreeIterator: error in parse tree!");
        OSL_ENSURE(pTableExp->count() == TABLE_EXPRESSION_CHILD_COUNT,"OSQLParseTreeIterator: error in parse tree!");

        pWhereClause = pTableExp->getChild(1);
    }
    else if (SQL_ISRULE(m_pParseTree,update_statement_searched) ||
             SQL_ISRULE(m_pParseTree,delete_statement_searched))
    {
        pWhereClause = m_pParseTree->getChild(m_pParseTree->count()-1);
    }
    if(pWhereClause && pWhereClause->count() != 2)
        pWhereClause = nullptr;
    return pWhereClause;
}


const OSQLParseNode* OSQLParseTreeIterator::getOrderTree() const
{
    if (!m_pParseTree || getStatementType() != OSQLStatementType::Select)
        return nullptr;

    // Analyse parse tree (depending on statement type)
    // and set pointer to ORDER clause:

    assert(SQL_ISRULE(m_pParseTree, select_statement) || SQL_ISRULE(m_pParseTree, union_statement));

    auto pParseTree = m_pParseTree;
    if(SQL_ISRULE(m_pParseTree, union_statement))
    {
        assert(m_pParseTree->count() == 4);
        pParseTree = pParseTree->getChild(3);
        // since UNION is left-associative (at least in our grammar),
        // possibly the left-hand (m_pParseTree->getChild(0)) is a union_statement,
        // but the right hand cannot.
        assert(SQL_ISRULE(pParseTree, select_statement));
    }

    OSQLParseNode * pOrderClause = nullptr;
    OSL_ENSURE(pParseTree->count() == 4, "OSQLParseTreeIterator::getOrderTree: expected a SELECT, and a SELECT must have exactly four children");
    OSQLParseNode * pTableExp = pParseTree->getChild(3);
    assert(pTableExp != nullptr && "OSQLParseTreeIterator::getOrderTree: got NULL table_exp");
    OSL_ENSURE(SQL_ISRULE(pTableExp, table_exp), "OSQLParseTreeIterator::getOrderTree: expected table_exp but got something else");
    OSL_ENSURE(pTableExp->count() == TABLE_EXPRESSION_CHILD_COUNT,"OSQLParseTreeIterator::getOrderTree: table_exp doesn't have the expected number of children");
    // tdf#141115 upgrade the above to an assert;
    // this cannot go well if there are too few children
    assert(pTableExp->count() == TABLE_EXPRESSION_CHILD_COUNT);

    pOrderClause = pTableExp->getChild(ORDER_BY_CHILD_POS);
    // If it is an order_by, it must not be empty
    if(pOrderClause->count() != 3)
        pOrderClause = nullptr;
    return pOrderClause;
}

const OSQLParseNode* OSQLParseTreeIterator::getGroupByTree() const
{
    if (!m_pParseTree || getStatementType() != OSQLStatementType::Select)
        return nullptr;

    // Analyse parse tree (depending on statement type)
    // and set pointer to ORDER clause:
    OSQLParseNode * pGroupClause = nullptr;
    OSL_ENSURE(m_pParseTree->count() >= 4,"ParseTreeIterator: error in parse tree!");
    OSQLParseNode * pTableExp = m_pParseTree->getChild(3);
    assert(pTableExp != nullptr && "OSQLParseTreeIterator: error in parse tree!");
    OSL_ENSURE(SQL_ISRULE(pTableExp,table_exp),"OSQLParseTreeIterator: error in parse tree!");
    OSL_ENSURE(pTableExp->count() == TABLE_EXPRESSION_CHILD_COUNT,"OSQLParseTreeIterator: error in parse tree!");

    pGroupClause = pTableExp->getChild(2);
    // If it is an order_by, it must not be empty
    if(pGroupClause->count() != 3)
        pGroupClause = nullptr;
    return pGroupClause;
}

const OSQLParseNode* OSQLParseTreeIterator::getHavingTree() const
{
    if (!m_pParseTree || getStatementType() != OSQLStatementType::Select)
        return nullptr;

    // Analyse parse tree (depending on statement type)
    // and set pointer to ORDER clause:
    OSQLParseNode * pHavingClause = nullptr;
    OSL_ENSURE(m_pParseTree->count() >= 4,"ParseTreeIterator: error in parse tree!");
    OSQLParseNode * pTableExp = m_pParseTree->getChild(3);
    assert(pTableExp != nullptr && "OSQLParseTreeIterator: error in parse tree!");
    OSL_ENSURE(SQL_ISRULE(pTableExp,table_exp),"OSQLParseTreeIterator: error in parse tree!");
    OSL_ENSURE(pTableExp->count() == TABLE_EXPRESSION_CHILD_COUNT,"OSQLParseTreeIterator: error in parse tree!");

    pHavingClause = pTableExp->getChild(3);
    // If it is an order_by, then it must not be empty
    if(pHavingClause->count() < 1)
        pHavingClause = nullptr;
    return pHavingClause;
}

bool OSQLParseTreeIterator::isTableNode(const OSQLParseNode* _pTableNode)
{
    return _pTableNode && (SQL_ISRULE(_pTableNode,catalog_name) ||
                           SQL_ISRULE(_pTableNode,schema_name)  ||
                           SQL_ISRULE(_pTableNode,table_name));
}

const OSQLParseNode* OSQLParseTreeIterator::getSimpleWhereTree() const
{
    const OSQLParseNode* pNode = getWhereTree();
    return pNode ? pNode->getChild(1) : nullptr;
}

const OSQLParseNode* OSQLParseTreeIterator::getSimpleOrderTree() const
{
    const OSQLParseNode* pNode = getOrderTree();
    return pNode ? pNode->getChild(2) : nullptr;
}

const OSQLParseNode* OSQLParseTreeIterator::getSimpleGroupByTree() const
{
    const OSQLParseNode* pNode = getGroupByTree();
    return pNode ? pNode->getChild(2) : nullptr;
}

const OSQLParseNode* OSQLParseTreeIterator::getSimpleHavingTree() const
{
    const OSQLParseNode* pNode = getHavingTree();
    return pNode ? pNode->getChild(1) : nullptr;
}


Reference< XPropertySet > OSQLParseTreeIterator::findSelectColumn( std::u16string_view rColumnName )
{
    for (auto const& lookupColumn : *m_aSelectColumns)
    {
        Reference< XPropertySet > xColumn( lookupColumn );
        try
        {
            OUString sName;
            xColumn->getPropertyValue( OMetaConnection::getPropMap().getNameByIndex( PROPERTY_ID_NAME ) ) >>= sName;
            if ( sName == rColumnName )
                return xColumn;
        }
        catch( const Exception& )
        {
            DBG_UNHANDLED_EXCEPTION("connectivity.parse");
        }
    }
    return nullptr;
}


Reference< XPropertySet > OSQLParseTreeIterator::findColumn( const OUString & rColumnName, OUString & rTableRange, bool _bLookInSubTables )
{
    Reference< XPropertySet > xColumn = findColumn( *m_pImpl->m_pTables, rColumnName, rTableRange );
    if ( !xColumn.is() && _bLookInSubTables )
        xColumn = findColumn( *m_pImpl->m_pSubTables, rColumnName, rTableRange );
    return xColumn;
}


Reference< XPropertySet > OSQLParseTreeIterator::findColumn(const OSQLTables& _rTables, const OUString & rColumnName, OUString & rTableRange)
{
    Reference< XPropertySet > xColumn;
    if ( !rTableRange.isEmpty() )
    {
        OSQLTables::const_iterator aFind = _rTables.find(rTableRange);

        if ( aFind != _rTables.end()
            && aFind->second.is()
            && aFind->second->getColumns().is()
            && aFind->second->getColumns()->hasByName(rColumnName) )
            aFind->second->getColumns()->getByName(rColumnName) >>= xColumn;
    }
    if ( !xColumn.is() )
    {
        for (auto const& table : _rTables)
        {
            if ( table.second.is() )
            {
                Reference<XNameAccess> xColumns = table.second->getColumns();
                if( xColumns.is() && xColumns->hasByName(rColumnName) && (xColumns->getByName(rColumnName) >>= xColumn) )
                {
                    OSL_ENSURE(xColumn.is(),"Column isn't a propertyset!");
                    // Cannot take "rTableRange = table.first" because that is the fully composed name
                    // that is, catalogName.schemaName.tableName
                    rTableRange = getString(xColumn->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_TABLENAME)));
                    break; // This column must only exits once
                }
            }
        }
    }
    return xColumn;
}


void OSQLParseTreeIterator::impl_appendError( IParseContext::ErrorCode _eError, const OUString* _pReplaceToken1, const OUString* _pReplaceToken2 )
{
    OUString sErrorMessage = m_rParser.getContext().getErrorMessage( _eError );
    if ( _pReplaceToken1 )
    {
        bool bTwoTokens = ( _pReplaceToken2 != nullptr );
        const char* pPlaceHolder1 = bTwoTokens ? "#1" : "#";
        const OUString sPlaceHolder1 = OUString::createFromAscii( pPlaceHolder1 );

        sErrorMessage = sErrorMessage.replaceFirst( sPlaceHolder1, *_pReplaceToken1 );
        if ( _pReplaceToken2 )
            sErrorMessage = sErrorMessage.replaceFirst( "#2" , *_pReplaceToken2 );
    }

    impl_appendError( SQLException(
        sErrorMessage, nullptr, getStandardSQLState( StandardSQLState::GENERAL_ERROR ), 1000, Any() ) );
}


void OSQLParseTreeIterator::impl_appendError( const SQLException& _rError )
{
    SAL_WARN("connectivity.parse", "Adding error " << exceptionToString(Any(_rError)));
    if ( m_xErrors )
    {
        SQLException* pErrorChain = &*m_xErrors;
        while ( pErrorChain->NextException.hasValue() )
            pErrorChain = static_cast< SQLException* >( pErrorChain->NextException.pData );
        pErrorChain->NextException <<= _rError;
    }
    else
        m_xErrors = _rError;
}

sal_Int32 OSQLParseTreeIterator::getFunctionReturnType(const OSQLParseNode* _pNode )
{
    sal_Int32 nType = DataType::OTHER;
    OUString sFunctionName;
    if ( SQL_ISRULE(_pNode,length_exp) )
    {
        _pNode->getChild(0)->getChild(0)->parseNodeToStr(sFunctionName, m_pImpl->m_xConnection, nullptr, false, false );
        nType = ::connectivity::OSQLParser::getFunctionReturnType( sFunctionName, &m_rParser.getContext() );
    }
    else if ( SQL_ISRULE(_pNode,num_value_exp) || SQL_ISRULE(_pNode,term) || SQL_ISRULE(_pNode,factor) )
    {
        nType = DataType::DOUBLE;
    }
    else
    {
        _pNode->getChild(0)->parseNodeToStr(sFunctionName, m_pImpl->m_xConnection, nullptr, false, false );

        // MIN and MAX have another return type, we have to check the expression itself.
        // @see http://qa.openoffice.org/issues/show_bug.cgi?id=99566
        if ( SQL_ISRULE(_pNode,general_set_fct) && (SQL_ISTOKEN(_pNode->getChild(0),MIN) || SQL_ISTOKEN(_pNode->getChild(0),MAX) ))
        {
            const OSQLParseNode* pValueExp = _pNode->getChild(3);
            if (SQL_ISRULE(pValueExp,column_ref))
            {
                OUString sColumnName;
                OUString aTableRange;
                getColumnRange(pValueExp,sColumnName,aTableRange);
                OSL_ENSURE(!sColumnName.isEmpty(),"Columnname must not be empty!");
                Reference<XPropertySet> xColumn = findColumn( sColumnName, aTableRange, true );

                if ( xColumn.is() )
                {
                    xColumn->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex( PROPERTY_ID_TYPE)) >>= nType;
                }
            }
            else
            {
                if ( SQL_ISRULE(pValueExp,num_value_exp) || SQL_ISRULE(pValueExp,term) || SQL_ISRULE(pValueExp,factor) )
                {
                    nType = DataType::DOUBLE;
                }
                else if ( SQL_ISRULE(pValueExp,datetime_primary) )
                {
                    switch(pValueExp->getChild(0)->getTokenID() )
                    {
                        case SQL_TOKEN_CURRENT_DATE:
                            nType = DataType::DATE;
                            break;
                        case SQL_TOKEN_CURRENT_TIME:
                            nType = DataType::TIME;
                            break;
                        case SQL_TOKEN_CURRENT_TIMESTAMP:
                            nType = DataType::TIMESTAMP;
                            break;
                    }
                }
                else if ( SQL_ISRULE(pValueExp,value_exp_primary) )
                {
                    nType = getFunctionReturnType(pValueExp->getChild(1));
                }
                else if ( SQL_ISRULE(pValueExp,concatenation)
                        || SQL_ISRULE(pValueExp,char_factor)
                        || SQL_ISRULE(pValueExp,bit_value_fct)
                        || SQL_ISRULE(pValueExp,char_value_fct)
                        || SQL_ISRULE(pValueExp,char_substring_fct)
                        || SQL_ISRULE(pValueExp,fold)
                        || SQL_ISTOKEN(pValueExp,STRING) )
                {
                    nType = DataType::VARCHAR;
                }
            }
            if ( nType == DataType::OTHER )
                nType = DataType::DOUBLE;
        }
        else
        {
            nType = ::connectivity::OSQLParser::getFunctionReturnType( sFunctionName, &m_rParser.getContext() );
            if (nType == DataType::SQLNULL)
                nType = ::connectivity::OSQLParser::getFunctionReturnType( sFunctionName, m_rParser.getNeutral() );
        }
    }

    return nType;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
