/* -*- 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_SQLNODE_HXX
#define INCLUDED_CONNECTIVITY_SQLNODE_HXX

#include <connectivity/dbtoolsdllapi.hxx>
#include <connectivity/dbmetadata.hxx>
#include <com/sun/star/uno/Reference.hxx>
#include <memory>
#include <set>
#include <string_view>
#include <vector>
#include <rtl/ustrbuf.hxx>

namespace com::sun::star::lang { struct Locale; }
namespace com::sun::star::sdbc { class SQLException; }
namespace com::sun::star::sdbc { class XDatabaseMetaData; }

namespace com::sun::star
{
    namespace beans
    {
        class XPropertySet;
    }
    namespace util
    {
        class XNumberFormatter;
    }
    namespace container
    {
        class XNameAccess;
    }
}

#define ORDER_BY_CHILD_POS  5
#define TABLE_EXPRESSION_CHILD_COUNT    9

namespace connectivity
{
    class OSQLParser;
    class IParseContext;

    enum class SQLNodeType { Rule, ListRule, CommaListRule,
                         Keyword, Name,
                         String, IntNum, ApproxNum,
                         Equal, Less, Great, LessEq, GreatEq, NotEqual,
                         Punctuation, AccessDate, Concat};

    typedef ::std::set< OUString >   QueryNameSet;

    //= SQLParseNodeParameter

    struct SQLParseNodeParameter
    {
        const css::lang::Locale&                              rLocale;
        ::dbtools::DatabaseMetaData                           aMetaData;
        OSQLParser*                                           pParser;
        std::shared_ptr< QueryNameSet >                       pSubQueryHistory;
        css::uno::Reference< css::util::XNumberFormatter >    xFormatter;
        css::uno::Reference< css::beans::XPropertySet >       xField;
        OUString                                              sPredicateTableAlias;
        css::uno::Reference< css::container::XNameAccess >    xQueries;  // see bParseToSDBCLevel
        const IParseContext&                                  m_rContext;
        OUString            sDecSep;
        bool                bQuote                      : 1;    /// should we quote identifiers?
        bool                bInternational              : 1;    /// should we internationalize keywords and placeholders?
        bool                bPredicate                  : 1;    /// are we going to parse a mere predicate?
        bool                bParseToSDBCLevel           : 1;    /// should we create an SDBC-level statement (e.g. with substituted sub queries)?

        SQLParseNodeParameter(
            const css::uno::Reference< css::sdbc::XConnection >& _rxConnection,
            const css::uno::Reference< css::util::XNumberFormatter >& _xFormatter,
            const css::uno::Reference< css::beans::XPropertySet >& _xField,
            OUString _sPredicateTableAlias,
            const css::lang::Locale& _rLocale,
            const IParseContext* _pContext,
            bool _bIntl,
            bool _bQuote,
            OUString _sDecSep,
            bool _bPredicate,
            bool _bParseToSDBC
        );
    };

    //= OSQLParseNode

    class OOO_DLLPUBLIC_DBTOOLS OSQLParseNode
    {
        friend class OSQLParser;

        std::vector< std::unique_ptr<OSQLParseNode> >
                                 m_aChildren;
        OSQLParseNode*           m_pParent;      // pParent for reverse linkage in the tree
        OUString                 m_aNodeValue;   // token name, or empty in case of rules,
                                                 // or OUString in case of
                                                 // OUString, INT, etc.
        SQLNodeType              m_eNodeType;    // see above
        sal_uInt32               m_nNodeID;      // Rule ID (if IsRule())
                                                 // or Token ID (if !IsRule())
                                            // Rule IDs and Token IDs can't
                                            // be distinguished by their values,
                                            // IsRule has to be used for that!
    public:
        enum Rule
        {
            UNKNOWN_RULE = 0,  // ID indicating that a node is no rule with a matching Rule-enum value (see getKnownRuleID)
                               // we make sure it is 0 so that it is the default-constructor value of this enum
                               // and std::map<foo,Rule>::operator[](bar) default-inserts UNKNOWN_RULE rather than select_statement (!)
            select_statement,
            table_exp,
            table_ref_commalist,
            table_ref,
            catalog_name,
            schema_name,
            table_name,
            opt_column_commalist,
            column_commalist,
            column_ref_commalist,
            column_ref,
            opt_order_by_clause,
            ordering_spec_commalist,
            ordering_spec,
            opt_asc_desc,
            where_clause,
            opt_where_clause,
            search_condition,
            comparison,
            comparison_predicate,
            between_predicate,
            like_predicate,
            opt_escape,
            test_for_null,
            scalar_exp_commalist,
            scalar_exp,
            parameter_ref,
            parameter,
            general_set_fct,
            range_variable,
            column,
            delete_statement_positioned,
            delete_statement_searched,
            update_statement_positioned,
            update_statement_searched,
            assignment_commalist,
            assignment,
            values_or_query_spec,
            insert_statement,
            insert_atom_commalist,
            insert_atom,
            from_clause,
            qualified_join,
            cross_union,
            select_sublist,
            derived_column,
            column_val,
            set_fct_spec,
            boolean_term,
            boolean_primary,
            num_value_exp,
            join_type,
            position_exp,
            extract_exp,
            length_exp,
            char_value_fct,
            odbc_call_spec,
            in_predicate,
            existence_test,
            unique_test,
            all_or_any_predicate,
            named_columns_join,
            join_condition,
            joined_table,
            boolean_factor,
            sql_not,
            manipulative_statement,
            subquery,
            value_exp_commalist,
            odbc_fct_spec,
            union_statement,
            outer_join_type,
            char_value_exp,
            term,
            value_exp_primary,
            value_exp,
            selection,
            fold,
            char_substring_fct,
            factor,
            base_table_def,
            base_table_element_commalist,
            data_type,
            column_def,
            table_node,
            as_clause,
            opt_as,
            op_column_commalist,
            table_primary_as_range_column,
            datetime_primary,
            concatenation,
            char_factor,
            bit_value_fct,
            comparison_predicate_part_2,
            parenthesized_boolean_value_expression,
            character_string_type,
            other_like_predicate_part_2,
            between_predicate_part_2,
            null_predicate_part_2,
            cast_spec,
            window_function,
            rule_count             // last value
        };

        // must be ascii encoding for the value
        OSQLParseNode(const char* _pValueStr,
                      SQLNodeType _eNodeType,
                      sal_uInt32 _nNodeID = 0);

        OSQLParseNode(std::string_view _rValue,
                      SQLNodeType eNewNodeType,
                      sal_uInt32 nNewNodeID=0);

        OSQLParseNode(OUString _sValue,
                      SQLNodeType _eNodeType,
                      sal_uInt32 _nNodeID = 0);

            // copies the respective ParseNode
        OSQLParseNode(const OSQLParseNode& rParseNode);
        OSQLParseNode& operator=(const OSQLParseNode& rParseNode);

        bool operator==(OSQLParseNode const & rParseNode) const;

        // destructor destructs the tree recursively
        virtual ~OSQLParseNode();

        OSQLParseNode* getParent() const {return m_pParent;};

        void setParent(OSQLParseNode* pParseNode) {m_pParent = pParseNode;};

        size_t count() const {return m_aChildren.size();};
        inline OSQLParseNode* getChild(sal_uInt32 nPos) const;

        void append(OSQLParseNode* pNewSubTree);
        void insert(sal_uInt32 nPos, OSQLParseNode* pNewSubTree);

        void replaceAndDelete(OSQLParseNode* pOldSubTree, OSQLParseNode* pNewSubTree);

        OSQLParseNode* removeAt(sal_uInt32 nPos);

        void replaceNodeValue(const OUString& rTableAlias,const OUString& rColumnName);

        /** parses the node to a string which can be passed to a driver's connection for execution

            Any particles of the parse tree which represent application-level features - such
            as queries appearing in the FROM part - are substituted, so that the resulting statement can
            be executed at an SDBC-level connection.

            @param  _out_rString
                is an output parameter taking the resulting SQL statement

            @param  _rxConnection
                the connection relative to which to parse. This must be an SDB-level connection (e.g.
                support the XQueriesSupplier interface) for the method to be able to do all necessary
                substitutions.

            @param _rParser
                the SQLParser used to create the node. This is needed in case we need to parse
                sub queries which are present in the SQL statement - those sub queries need to be parsed,
                too, to check whether they contain nested sub queries.

            @param _pErrorHolder
                takes the error which occurred while generating the statement, if any. Might be <NULL/>,
                in this case the error is not reported back, and can only be recognized by examining the
                return value.

            @return
                <TRUE/> if and only if the parsing was successful.<br/>

                Currently, there's only one condition how this method can fail: If it contains a nested
                query which causes a cycle. E.g., consider a statement <code>SELECT * from "foo"</code>,
                where <code>foo</code> is a query defined as <code>SELECT * FROM "bar"</code>, where
                <code>bar</code> is defined as <code>SELECT * FROM "foo"</code>. This statement obviously
                cannot be parsed to an executable statement.

                If this method returns <FALSE/>, you're encouraged to check and handle the error in
                <arg>_pErrorHolder</arg>.
        */
        bool parseNodeToExecutableStatement( OUString& _out_rString,
            const css::uno::Reference< css::sdbc::XConnection >& _rxConnection,
            OSQLParser& _rParser,
            css::sdbc::SQLException* _pErrorHolder ) const;

        void parseNodeToStr(OUString& rString,
                            const css::uno::Reference< css::sdbc::XConnection >& _rxConnection,
                            const IParseContext* pContext = nullptr,
                            bool _bIntl = false,
                            bool _bQuote= true) const;

        // quoted and internationalised
        void parseNodeToPredicateStr(OUString& rString,
                                     const css::uno::Reference< css::sdbc::XConnection >& _rxConnection,
                                     const css::uno::Reference< css::util::XNumberFormatter > & xFormatter,
                                     const css::lang::Locale& rIntl,
                                     const OUString& rDec,
                                     const IParseContext* pContext = nullptr ) const;

        void parseNodeToPredicateStr(OUString& rString,
                                     const css::uno::Reference< css::sdbc::XConnection >& _rxConnection,
                                     const css::uno::Reference< css::util::XNumberFormatter > & xFormatter,
                                     const css::uno::Reference< css::beans::XPropertySet > & _xField,
                                     const OUString &_sTableAlias,
                                     const css::lang::Locale& rIntl,
                                     const OUString& rStrDec,
                                     const IParseContext* pContext = nullptr ) const;

        OSQLParseNode* getByRule(OSQLParseNode::Rule eRule) const;

#if OSL_DEBUG_LEVEL > 1
        // shows the ParseTree with tabs and linefeeds
        void showParseTree( OUString& rString ) const;
        void showParseTree( OUStringBuffer& _inout_rBuf, sal_uInt32 nLevel ) const;
#endif

        SQLNodeType getNodeType() const {return m_eNodeType;};

        // RuleId returns the RuleID of the node's rule (only if IsRule())
        sal_uInt32 getRuleID() const {return m_nNodeID;}

        /** returns the ID of the rule represented by the node
            If the node does not represent a rule, UNKNOWN_RULE is returned
        */
        Rule getKnownRuleID() const;

            // returns the TokenId of the node's token (only if !isRule())
        sal_uInt32 getTokenID() const {return m_nNodeID;}

            // IsRule tests whether a node is a rule (NonTerminal)
            // ATTENTION: rules can be leaves, for example empty lists
        bool isRule() const
            { return (m_eNodeType == SQLNodeType::Rule) || (m_eNodeType == SQLNodeType::ListRule)
                || (m_eNodeType == SQLNodeType::CommaListRule);}

            // IsToken tests whether a Node is a Token (Terminal but not a rule)
        bool isToken() const {return !isRule();}

        const OUString& getTokenValue() const {return m_aNodeValue;}

        bool isLeaf() const {return m_aChildren.empty();}

        // negate only a searchcondition, any other rule could cause a gpf
        static void negateSearchCondition(OSQLParseNode*& pSearchCondition, bool bNegate=false);

        // normalize a logic form
        // e.q. (a or b) and (c or d) <=> a and c or a and d or b and c or b and d
        static void disjunctiveNormalForm(OSQLParseNode*& pSearchCondition);

        //   Simplifies logic expressions
        // a and a        = a
        // a or a         = a
        // a and ( a + b) = a
        // a or a and b   = a
        static void absorptions(OSQLParseNode*& pSearchCondition);

        // erase unnecessary braces
        static void eraseBraces(OSQLParseNode*& pSearchCondition);

        // makes the logic formula a little smaller
        static void compress(OSQLParseNode*& pSearchCondition);
        // return the catalog, schema and tablename from this node
        // _pTableNode must be a rule of that above or a SQL_TOKEN_NAME
        static bool getTableComponents(const OSQLParseNode* _pTableNode,
                                            css::uno::Any &_rCatalog,
                                            OUString &_rSchema,
                                            OUString &_rTable,
                                            const css::uno::Reference< css::sdbc::XDatabaseMetaData >& _xMetaData);

        // substitute all occurrences of :var or [name] into the dynamic parameter ?
        // _pNode will be modified if parameters exists
        static void substituteParameterNames(OSQLParseNode const * _pNode);

        /** return a table range when it exists.
        */
        static OUString getTableRange(const OSQLParseNode* _pTableRef);

    protected:
        // ParseNodeToStr concatenates all Tokens (leaves) of the ParseNodes.
        void parseNodeToStr(OUString& rString,
                            const css::uno::Reference< css::sdbc::XConnection >& _rxConnection,
                            const css::uno::Reference< css::util::XNumberFormatter > & xFormatter,
                            const css::uno::Reference< css::beans::XPropertySet > & _xField,
                            const OUString &_sPredicateTableAlias,
                            const css::lang::Locale& rIntl,
                            const IParseContext* pContext,
                            bool _bIntl,
                            bool _bQuote,
                            const OUString& _rDecSep,
                            bool _bPredicate) const;

    private:
        void impl_parseNodeToString_throw( OUStringBuffer& rString, const SQLParseNodeParameter& rParam, bool bSimple=true ) const;
        void impl_parseLikeNodeToString_throw( OUStringBuffer& rString, const SQLParseNodeParameter& rParam, bool bSimple=true ) const;
        void impl_parseTableRangeNodeToString_throw( OUStringBuffer& rString, const SQLParseNodeParameter& rParam ) const;

        /** parses a table_name node into a SQL statement particle.
            @return
                <TRUE/> if and only if parsing was successful, <FALSE/> if default handling should
                be applied.
        */
        bool impl_parseTableNameNodeToString_throw( OUStringBuffer& rString, const SQLParseNodeParameter& rParam ) const;

        bool addDateValue(OUStringBuffer& rString, const SQLParseNodeParameter& rParam) const;
        static OUString convertDateTimeString(const SQLParseNodeParameter& rParam, const OUString& rString);
        static OUString convertDateString(const SQLParseNodeParameter& rParam, std::u16string_view rString);
        static OUString convertTimeString(const SQLParseNodeParameter& rParam, std::u16string_view rString);
        void parseLeaf(OUStringBuffer& rString, const SQLParseNodeParameter& rParam) const;
    };

    inline OSQLParseNode* OSQLParseNode::getChild(sal_uInt32 nPos) const
    {
        return m_aChildren[nPos].get();
    }

    // utilities to query for a specific rule, token or punctuation
    #define SQL_ISRULE(pParseNode, eRule)   ((pParseNode)->isRule() && (pParseNode)->getRuleID() == OSQLParser::RuleID(OSQLParseNode::eRule))
    #define SQL_ISRULEOR2(pParseNode, e1, e2)  ((pParseNode)->isRule() && ( \
                                                  (pParseNode)->getRuleID() == OSQLParser::RuleID(OSQLParseNode::e1) || \
                                                  (pParseNode)->getRuleID() == OSQLParser::RuleID(OSQLParseNode::e2)))
    #define SQL_ISRULEOR3(pParseNode, e1, e2, e3)  ((pParseNode)->isRule() && ( \
                                                      (pParseNode)->getRuleID() == OSQLParser::RuleID(OSQLParseNode::e1) || \
                                                      (pParseNode)->getRuleID() == OSQLParser::RuleID(OSQLParseNode::e2) || \
                                                      (pParseNode)->getRuleID() == OSQLParser::RuleID(OSQLParseNode::e3)))
    #define SQL_ISTOKEN(pParseNode, token) ((pParseNode)->isToken() && (pParseNode)->getTokenID() == SQL_TOKEN_##token)
    #define SQL_ISTOKENOR2(pParseNode, tok0, tok1) ((pParseNode)->isToken() &&  ( (pParseNode)->getTokenID() == SQL_TOKEN_##tok0 || (pParseNode)->getTokenID() == SQL_TOKEN_##tok1 ))
    #define SQL_ISTOKENOR3(pParseNode, tok0, tok1, tok2) ((pParseNode)->isToken() && ( (pParseNode)->getTokenID() == SQL_TOKEN_##tok0 || (pParseNode)->getTokenID() == SQL_TOKEN_##tok1 || (pParseNode)->getTokenID() == SQL_TOKEN_##tok2 ))
    #define SQL_ISPUNCTUATION(pParseNode, aString) ((pParseNode)->getNodeType() == SQLNodeType::Punctuation && (pParseNode)->getTokenValue() == (aString))
}

#endif // INCLUDED_CONNECTIVITY_SQLNODE_HXX

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