/* -*- 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 .
 */
#ifndef INCLUDED_CONNECTIVITY_SQLITERATOR_HXX
#define INCLUDED_CONNECTIVITY_SQLITERATOR_HXX

#include <connectivity/dbtoolsdllapi.hxx>
#include <connectivity/sqlnode.hxx>
#include <connectivity/IParseContext.hxx>
#include <com/sun/star/sdbcx/XColumnsSupplier.hpp>
#include <com/sun/star/sdbc/DataType.hpp>
#include <com/sun/star/sdbc/SQLWarning.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <connectivity/CommonTools.hxx>
#include <rtl/ref.hxx>
#include <cppuhelper/weak.hxx>

#include <map>
#include <memory>
#include <vector>

namespace connectivity
{

    class OSQLParseNode;
    class OSQLParser;

    typedef ::std::pair<const OSQLParseNode*,const OSQLParseNode* > TNodePair;

    enum OSQLStatementType {
        SQL_STATEMENT_UNKNOWN,
        SQL_STATEMENT_SELECT,
        SQL_STATEMENT_INSERT,
        SQL_STATEMENT_UPDATE,
        SQL_STATEMENT_DELETE,
        SQL_STATEMENT_ODBC_CALL,
        SQL_STATEMENT_CREATE_TABLE
    };

    struct OSQLParseTreeIteratorImpl;

    class OOO_DLLPUBLIC_DBTOOLS OSQLParseTreeIterator
    {
    private:
        ::com::sun::star::sdbc::SQLException            m_aErrors;          // conatins the error while iterating through the statement
        const OSQLParseNode*                            m_pParseTree;       // current ParseTree
        const OSQLParser&                               m_rParser;          // if set used for general error messages from the context
        OSQLStatementType                               m_eStatementType;
        ::rtl::Reference<OSQLColumns>                       m_aSelectColumns;   // all columns from the Select clause
        ::rtl::Reference<OSQLColumns>                       m_aParameters;      // all parameters
        ::rtl::Reference<OSQLColumns>                       m_aGroupColumns;    // the group by columns
        ::rtl::Reference<OSQLColumns>                       m_aOrderColumns;    // the order by columns
        ::rtl::Reference<OSQLColumns>                       m_aCreateColumns;   // the columns for Create table clause

        ::std::unique_ptr< OSQLParseTreeIteratorImpl >  m_pImpl;

        void                traverseParameter(const OSQLParseNode* _pParseNode,const OSQLParseNode* _pColumnRef,const OUString& _aColumnName, OUString& _aTableRange, const OUString& _rColumnAlias);
        // inserts a table into the map
        void                traverseOneTableName( OSQLTables& _rTables,const OSQLParseNode * pTableName, const OUString & rTableRange );
        void                traverseSearchCondition(OSQLParseNode * pSearchCondition);
        void                traverseOnePredicate(
                                                OSQLParseNode * pColumnRef,
                                                OUString& aValue,
                                                OSQLParseNode * pParameter);
        void traverseByColumnNames(const OSQLParseNode* pSelectNode, bool _bOrder);
        void                traverseParameters(const OSQLParseNode* pSelectNode);

        const OSQLParseNode*    getTableNode( OSQLTables& _rTables, const OSQLParseNode* pTableRef, OUString& aTableRange );
        void                    getQualified_join( OSQLTables& _rTables, const OSQLParseNode *pTableRef, OUString& aTableRange );
        void                    getSelect_statement(OSQLTables& _rTables,const OSQLParseNode* pSelect);
        OUString         getUniqueColumnName(const OUString & rColumnName)    const;

        /** finds the column with a given name, belonging to a given table, in a given tables collection
            @param  _rTables
                the tables collection to look in
            @param  rColumnName
                the column name to look for
            @param  rTableRange
                the table alias name; if empty, look in all tables
            @return
                the desired column object, or <NULL/> if no such column could be found
        */
        static ::com::sun::star::uno::Reference< ::com::sun::star::beans::XPropertySet > findColumn(
            const OSQLTables& _rTables, const OUString & rColumnName, OUString & rTableRange );

        /** finds a column with a given name, belonging to a given table
            @param  rColumnName
                the column name to look for
            @param  rTableRange
                    the table alias name; if empty, look in all tables
            @param  _bLookInSubTables
                <TRUE/> if and only if not only our direct tables, but also our sub tables (from sub selects)
                should be searched
            @return
        */
        ::com::sun::star::uno::Reference< ::com::sun::star::beans::XPropertySet > findColumn(
            const OUString & rColumnName, OUString & rTableRange, bool _bLookInSubTables );

        /** finds a column with a given name among the select columns
            @param  rColumnName
                the column name to look for
            @return
        */
        ::com::sun::star::uno::Reference< ::com::sun::star::beans::XPropertySet > findSelectColumn(
            const OUString & rColumnName );

      protected:
        void setSelectColumnName(::rtl::Reference<OSQLColumns>& _rColumns,const OUString & rColumnName,const OUString & rColumnAlias, const OUString & rTableRange, bool bFkt=false, sal_Int32 _nType = com::sun::star::sdbc::DataType::VARCHAR, bool bAggFkt=false);
        void appendColumns(::rtl::Reference<OSQLColumns>& _rColumns,const OUString& _rTableAlias,const OSQLTable& _rTable);
        // Other member variables that should be available in the "set" functions
        // can be defined in the derived class. They can be initialized
        // in its constructor and, after the "traverse" routines have been used,
        // they can be queried using other functions.


      private:
        OSQLParseTreeIterator();                                        // never implemented
        OSQLParseTreeIterator(const OSQLParseTreeIterator & rIter);     // never implemented

      public:
        OSQLParseTreeIterator(
            const ::com::sun::star::uno::Reference< ::com::sun::star::sdbc::XConnection >& _rxConnection,
            const ::com::sun::star::uno::Reference< ::com::sun::star::container::XNameAccess >& _rxTables,
            const OSQLParser& _rParser,
            const OSQLParseNode* pRoot = NULL );
        ~OSQLParseTreeIterator();

        inline static void * SAL_CALL operator new( size_t nSize )
            { return ::rtl_allocateMemory( nSize ); }
        inline static void * SAL_CALL operator new( size_t,void* _pHint )
            { return _pHint; }
        inline static void SAL_CALL operator delete( void * pMem )
            { ::rtl_freeMemory( pMem ); }
        inline static void SAL_CALL operator delete( void *,void* )
            {  }

        void dispose();
        bool isCaseSensitive() const;
        // The parse tree to be analysed/traversed:
        // If NULL is passed, the current parse tree will be deleted and the error status cleared.
        void setParseTree(const OSQLParseNode * pNewParseTree);
//      void setParser(const OSQLParser* _pParser) { m_pParser = _pParser; }
        const OSQLParseNode * getParseTree() const { return m_pParseTree; };

        // subtrees in case of a select statement
        const OSQLParseNode* getWhereTree() const;
        const OSQLParseNode* getOrderTree() const;
        const OSQLParseNode* getGroupByTree() const;
        const OSQLParseNode* getHavingTree() const;

        const OSQLParseNode* getSimpleWhereTree() const;
        const OSQLParseNode* getSimpleOrderTree() const;
        const OSQLParseNode* getSimpleGroupByTree() const;
        const OSQLParseNode* getSimpleHavingTree() const;

        /** returns the errors which occurred during parsing.

            The returned object contains a chain (via SQLException::NextException) of SQLExceptions.
        */
        inline const ::com::sun::star::sdbc::SQLException&   getErrors() const { return m_aErrors; }
        inline bool hasErrors() const { return !m_aErrors.Message.isEmpty(); }

        // statement type (already set in setParseTree):
        OSQLStatementType getStatementType() const { return m_eStatementType; }

        /** traverses the complete statement tree, and fills all our data with
            the information obatined during traversal.

            Implemented by calling the single traverse* methods in the proper
            order (depending on the statement type).
        */
        void traverseAll();

        enum TraversalParts
        {
            Parameters      = 0x0001,
            TableNames      = 0x0002,
            SelectColumns   = 0x0006,   // note that this includes TableNames. No SelectColumns without TableNames

            // Those are not implemented currently
            // GroupColumns    = 0x0008,
            // OrderColumns    = 0x0010,
            // SelectColumns   = 0x0020,
            // CreateColumns   = 0x0040,

            All             = 0xFFFF
        };
        /** traverses selected parts of the statement tree, and fills our data with
            the information obtained during traversal

            @param _nIncludeMask
                set of TraversalParts bits, specifying which information is to be collected.
                Note TraversalParts is currently not
        */
        void traverseSome( sal_uInt32 _nIncludeMask );

        // The TableRangeMap contains all tables associated with the range name found first.
        const OSQLTables& getTables() const;

        ::rtl::Reference<OSQLColumns> getSelectColumns() const { return m_aSelectColumns;}
        ::rtl::Reference<OSQLColumns> getGroupColumns() const { return m_aGroupColumns;}
        ::rtl::Reference<OSQLColumns> getOrderColumns() const { return m_aOrderColumns;}
        ::rtl::Reference<OSQLColumns> getParameters()   const { return m_aParameters; }
        ::rtl::Reference<OSQLColumns> getCreateColumns() const { return m_aCreateColumns;}

        /** return the columname and the table range
            @param  _pColumnRef
                The column ref parse node.
            @param  _rColumnName
                The column name to be set.
            @param  _rTableRange
                The table range to be set.
        */
        void getColumnRange(    const OSQLParseNode* _pColumnRef,
                                OUString &_rColumnName,
                                OUString& _rTableRange) const;

        /** retrieves a column's name, table range, and alias

            @param  _pColumnRef
                The column_ref parse node.
            @param  _out_rColumnName
                The column name to be set.
            @param  _out_rTableRange
                The table range to be set.
            @param _out_rColumnAliasIfPresent
                If the column specified by _pColumnRef is part of the select columns, and contains a column alias there,
                this alias is returned here.
        */
        void getColumnRange(    const OSQLParseNode* _pColumnRef,
                                OUString& _out_rColumnName,
                                OUString& _out_rTableRange,
                                OUString& _out_rColumnAliasIfPresent
                                ) const;

        /** return the alias name of a column
            @param  _pDerivedColumn
                The parse node where SQL_ISRULE(_pDerivedColumn,derived_column) must be true
            @return
                The alias name of the column or an empty string.
        */
        static OUString getColumnAlias(const OSQLParseNode* _pDerivedColumn);

        /** return the columname and the table range
            @param  _pColumnRef
                The column ref parse node.
            @param  _xMetaData
                The database meta data.
            @param  _rColumnName
                The column name to be set.
            @param  _rTableRange
                The table range to be set.
        */
        static void getColumnRange( const OSQLParseNode* _pColumnRef,
                                    const ::com::sun::star::uno::Reference< ::com::sun::star::sdbc::XConnection >& _rxConnection,
                                    OUString &_rColumnName,
                                    OUString& _rTableRange);

        // empty if ambiguous
        bool getColumnTableRange(const OSQLParseNode* pNode, OUString &rTableRange) const;

        // return true when the tableNode is a rule like catalog_name, schema_name or table_name
        bool isTableNode(const OSQLParseNode* _pTableNode) const;

        // tries to find the correct type of the function
        sal_Int32 getFunctionReturnType(const OSQLParseNode* _pNode );

        // returns a lis of all joined columns
        ::std::vector< TNodePair >& getJoinConditions() const;

    private:
        // helper to implement getColumnTableRange
        bool impl_getColumnTableRange(const OSQLParseNode* pNode, OUString &rTableRange) const;

        /** traverses the list of table names, and filles _rTables
        */
        bool traverseTableNames( OSQLTables& _rTables );

        /// traverses columns in a SELECT statement
        bool traverseSelectColumnNames(const OSQLParseNode* pSelectNode);
        /// traverses columns in a CREATE TABLE statement
        void traverseCreateColumns(const OSQLParseNode* pSelectNode);

        bool traverseOrderByColumnNames(const OSQLParseNode* pSelectNode);
        bool traverseGroupByColumnNames(const OSQLParseNode* pSelectNode);

        bool traverseSelectionCriteria(const OSQLParseNode* pSelectNode);

    private:
        /** constructs a new iterator, which inherits some of the settings from a parent iterator
        */
        OSQLParseTreeIterator(
            const OSQLParseTreeIterator& _rParentIterator,
            const OSQLParser& _rParser,
            const OSQLParseNode* pRoot );

        /** creates a table object and inserts it into our tables collection

            only used when we're iterating through a CREATE TABLE statement
        */
        OSQLTable   impl_createTableObject(
            const OUString& rTableName, const OUString& rCatalogName, const OUString& rSchemaName );

        /** locates a record source (a table or query) with the given name
        */
        OSQLTable   impl_locateRecordSource(
            const OUString& _rComposedName
        );

        /** implementation for both traverseAll and traverseSome
        */
        void    impl_traverse( sal_uInt32 _nIncludeMask );

        /** retrieves the parameter columns of the given query
        */
        void    impl_getQueryParameterColumns( const OSQLTable& _rQuery );

        void setOrderByColumnName(const OUString & rColumnName, OUString & rTableRange, bool bAscending);
        void setGroupByColumnName(const OUString & rColumnName, OUString & rTableRange);

    private:
        /** appends an SQLException corresponding to the given error code to our error collection

            @param  _eError
                the code of the error which occurred
            @param  _pReplaceToken1
                if not <NULL/>, the first occurrence of '#' in the error message will be replaced
                with the given token
            @param  _pReplaceToken2
                if not <NULL/>, and if _rReplaceToken1 is not <NULL/>, the second occurrence of '#'
                in the error message will be replaced with _rReplaceToken2
        */
        void impl_appendError( IParseContext::ErrorCode _eError,
            const OUString* _pReplaceToken1 = NULL, const OUString* _pReplaceToken2 = NULL );

        /** appends an SQLException corresponding to the given error code to our error collection
        */
        void impl_appendError( const ::com::sun::star::sdbc::SQLException& _rError );

        /** resets our errors
        */
        inline void impl_resetErrors()
        {
            m_aErrors = ::com::sun::star::sdbc::SQLException();
        }
        void impl_fillJoinConditions(const OSQLParseNode* i_pJoinCondition);
    };
}

#endif // INCLUDED_CONNECTIVITY_SQLITERATOR_HXX

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