/******************************************************************************
 *
 * Component: OGR SQL Engine
 * Purpose: Implementation of the swq_expr_node class used to represent a
 *          node in an SQL expression.
 * Author: Frank Warmerdam <warmerdam@pobox.com>
 *
 ******************************************************************************
 * Copyright (C) 2010 Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#ifndef DOXYGEN_SKIP

#include "cpl_port.h"
#include "ogr_swq.h"

#include <cctype>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_multiproc.h"
#include "cpl_string.h"
#include "ogr_geometry.h"


/************************************************************************/
/*                           swq_expr_node()                            */
/************************************************************************/

swq_expr_node::swq_expr_node() = default;

/************************************************************************/
/*                          swq_expr_node(int)                          */
/************************************************************************/

swq_expr_node::swq_expr_node( int nValueIn ):
    int_value(nValueIn)
{
}

/************************************************************************/
/*                        swq_expr_node(GIntBig)                        */
/************************************************************************/

swq_expr_node::swq_expr_node( GIntBig nValueIn ):
    field_type(SWQ_INTEGER64),
    int_value(nValueIn)
{
}

/************************************************************************/
/*                        swq_expr_node(double)                         */
/************************************************************************/

swq_expr_node::swq_expr_node( double dfValueIn ):
    field_type(SWQ_FLOAT),
    float_value(dfValueIn)
{
}

/************************************************************************/
/*                        swq_expr_node(const char*)                    */
/************************************************************************/

swq_expr_node::swq_expr_node( const char *pszValueIn ):
    field_type(SWQ_STRING),
    is_null(pszValueIn == nullptr),
    string_value(CPLStrdup( pszValueIn ? pszValueIn : "" ))
{
}

/************************************************************************/
/*                      swq_expr_node(OGRGeometry *)                    */
/************************************************************************/

swq_expr_node::swq_expr_node( OGRGeometry *poGeomIn ):
    field_type(SWQ_GEOMETRY),
    is_null(poGeomIn == nullptr),
    geometry_value(poGeomIn ? poGeomIn->clone() : nullptr)
{
}

/************************************************************************/
/*                        swq_expr_node(swq_op)                         */
/************************************************************************/

swq_expr_node::swq_expr_node( swq_op eOp ):
    eNodeType(SNT_OPERATION),
    nOperation(eOp)
{
}

/************************************************************************/
/*                           ~swq_expr_node()                           */
/************************************************************************/

swq_expr_node::~swq_expr_node()

{
    CPLFree( table_name );
    CPLFree( string_value );

    for( int i = 0; i < nSubExprCount; i++ )
        delete papoSubExpr[i];
    CPLFree( papoSubExpr );
    delete geometry_value;
}

/************************************************************************/
/*                          MarkAsTimestamp()                           */
/************************************************************************/

void swq_expr_node::MarkAsTimestamp()

{
    CPLAssert( eNodeType == SNT_CONSTANT );
    CPLAssert( field_type == SWQ_STRING );
    field_type = SWQ_TIMESTAMP;
}

/************************************************************************/
/*                         PushSubExpression()                          */
/************************************************************************/

void swq_expr_node::PushSubExpression( swq_expr_node *child )

{
    nSubExprCount++;
    papoSubExpr = static_cast<swq_expr_node **>(
        CPLRealloc( papoSubExpr, sizeof(void*) * nSubExprCount ));

    papoSubExpr[nSubExprCount-1] = child;
}

/************************************************************************/
/*                       ReverseSubExpressions()                        */
/************************************************************************/

void swq_expr_node::ReverseSubExpressions()

{
    for( int i = 0; i < nSubExprCount / 2; i++ )
    {
        std::swap(papoSubExpr[i], papoSubExpr[nSubExprCount - i - 1]);
    }
}

/************************************************************************/
/*                               Check()                                */
/*                                                                      */
/*      Check argument types, etc.                                      */
/************************************************************************/

swq_field_type
swq_expr_node::Check( swq_field_list *poFieldList,
                      int bAllowFieldsInSecondaryTables,
                      int bAllowMismatchTypeOnFieldComparison,
                      swq_custom_func_registrar* poCustomFuncRegistrar,
                      int nDepth )

{
    if( nDepth == 32 )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Too many recursion levels in expression");
        return SWQ_ERROR;
    }

/* -------------------------------------------------------------------- */
/*      Otherwise we take constants literally.                          */
/* -------------------------------------------------------------------- */
    if( eNodeType == SNT_CONSTANT )
        return field_type;

/* -------------------------------------------------------------------- */
/*      If this is intended to be a field definition, but has not       */
/*      yet been looked up, we do so now.                               */
/* -------------------------------------------------------------------- */
    if( eNodeType == SNT_COLUMN && field_index == -1 )
    {
        field_index =
            swq_identify_field( table_name, string_value, poFieldList,
                                &field_type, &table_index );

        if( field_index < 0 )
        {
            if( table_name )
                CPLError( CE_Failure, CPLE_AppDefined,
                      R"("%s"."%s" not recognised as an available field.)",
                      table_name, string_value );
            else
                CPLError( CE_Failure, CPLE_AppDefined,
                      "\"%s\" not recognised as an available field.",
                      string_value );

            return SWQ_ERROR;
        }

        if( !bAllowFieldsInSecondaryTables && table_index != 0 )
        {
            CPLError(
                CE_Failure, CPLE_AppDefined,
                "Cannot use field '%s' of a secondary table in this context",
                string_value );
            return SWQ_ERROR;
        }
    }

    if( eNodeType == SNT_COLUMN )
        return field_type;

/* -------------------------------------------------------------------- */
/*      We are dealing with an operation - fetch the definition.        */
/* -------------------------------------------------------------------- */
    const swq_operation *poOp =
        (nOperation == SWQ_CUSTOM_FUNC && poCustomFuncRegistrar != nullptr ) ?
            poCustomFuncRegistrar->GetOperator(string_value) :
            swq_op_registrar::GetOperator(nOperation);

    if( poOp == nullptr )
    {
        if( nOperation == SWQ_CUSTOM_FUNC )
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Check(): Unable to find definition for operator %s.",
                      string_value );
        else
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Check(): Unable to find definition for operator %d.",
                      nOperation );
        return SWQ_ERROR;
    }

/* -------------------------------------------------------------------- */
/*      Check subexpressions first.                                     */
/* -------------------------------------------------------------------- */
    for( int i = 0; i < nSubExprCount; i++ )
    {
        if( papoSubExpr[i]->Check(poFieldList, bAllowFieldsInSecondaryTables,
                                  bAllowMismatchTypeOnFieldComparison,
                                  poCustomFuncRegistrar,
                                  nDepth + 1) == SWQ_ERROR )
            return SWQ_ERROR;
    }

/* -------------------------------------------------------------------- */
/*      Check this node.                                                */
/* -------------------------------------------------------------------- */
    field_type = poOp->pfnChecker( this, bAllowMismatchTypeOnFieldComparison );

    return field_type;
}

/************************************************************************/
/*                                Dump()                                */
/************************************************************************/

void swq_expr_node::Dump( FILE * fp, int depth )

{
    char spaces[60] = {};

    {
        int i = 0;  // Used after for.
        for( ; i < depth*2 && i < static_cast<int>(sizeof(spaces)) - 1; i++ )
            spaces[i] = ' ';
        spaces[i] = '\0';
    }

    if( eNodeType == SNT_COLUMN )
    {
        fprintf( fp, "%s  Field %d\n", spaces, field_index );
        return;
    }

    if( eNodeType == SNT_CONSTANT )
    {
        if( field_type == SWQ_INTEGER || field_type == SWQ_INTEGER64 ||
            field_type == SWQ_BOOLEAN )
            fprintf( fp, "%s  " CPL_FRMT_GIB "\n", spaces, int_value );
        else if( field_type == SWQ_FLOAT )
            fprintf( fp, "%s  %.15g\n", spaces, float_value );
        else if( field_type == SWQ_GEOMETRY )
        {
            if( geometry_value == nullptr )
                fprintf( fp, "%s  (null)\n", spaces );
            else
            {
                char* pszWKT = nullptr;
                geometry_value->exportToWkt(&pszWKT);
                fprintf( fp, "%s  %s\n", spaces, pszWKT );
                CPLFree(pszWKT);
            }
        }
        else
            fprintf( fp, "%s  %s\n", spaces, string_value );
        return;
    }

    CPLAssert( eNodeType == SNT_OPERATION );

    const swq_operation *op_def =
        swq_op_registrar::GetOperator( nOperation );
    if( op_def )
        fprintf( fp, "%s%s\n", spaces, op_def->pszName );
    else
        fprintf( fp, "%s%s\n", spaces, string_value );

    for( int i = 0; i < nSubExprCount; i++ )
        papoSubExpr[i]->Dump( fp, depth+1 );
}

/************************************************************************/
/*                       QuoteIfNecessary()                             */
/*                                                                      */
/*      Add quoting if necessary to unparse a string.                   */
/************************************************************************/

CPLString swq_expr_node::QuoteIfNecessary( const CPLString &osExpr,
                                           char chQuote )

{
    if( osExpr[0] == '_' )
        return Quote(osExpr, chQuote);
    if( osExpr == "*" )
        return osExpr;

    for( int i = 0; i < static_cast<int>(osExpr.size()); i++ )
    {
        char ch = osExpr[i];
        if( (!(isalnum(static_cast<int>(ch)) || ch == '_')) || ch == '.' )
        {
            return Quote(osExpr, chQuote);
        }
    }

    if( swq_is_reserved_keyword(osExpr) )
    {
        return Quote(osExpr, chQuote);
    }

    return osExpr;
}

/************************************************************************/
/*                               Quote()                                */
/*                                                                      */
/*      Add quoting necessary to unparse a string.                      */
/************************************************************************/

CPLString swq_expr_node::Quote( const CPLString &osTarget, char chQuote )

{
    CPLString osNew;

    osNew += chQuote;

    for( int i = 0; i < static_cast<int>(osTarget.size()); i++ )
    {
        if( osTarget[i] == chQuote )
        {
            osNew += chQuote;
            osNew += chQuote;
        }
        else
            osNew += osTarget[i];
    }
    osNew += chQuote;

    return osNew;
}

/************************************************************************/
/*                              Unparse()                               */
/************************************************************************/

char *swq_expr_node::Unparse( swq_field_list *field_list, char chColumnQuote )

{
    CPLString osExpr;

/* -------------------------------------------------------------------- */
/*      Handle constants.                                               */
/* -------------------------------------------------------------------- */
    if( eNodeType == SNT_CONSTANT )
    {
        if( is_null )
            return CPLStrdup("NULL");

        if( field_type == SWQ_INTEGER || field_type == SWQ_INTEGER64 ||
            field_type == SWQ_BOOLEAN )
            osExpr.Printf( CPL_FRMT_GIB, int_value );
        else if( field_type == SWQ_FLOAT )
        {
            osExpr.Printf( "%.15g", float_value );
            // Make sure this is interpreted as a floating point value
            // and not as an integer later.
            if( strchr(osExpr, '.') == nullptr && strchr(osExpr, 'e') == nullptr &&
                strchr(osExpr, 'E') == nullptr )
                osExpr += '.';
        }
        else
        {
            osExpr = Quote( string_value );
        }

        return CPLStrdup(osExpr);
    }

/* -------------------------------------------------------------------- */
/*      Handle columns.                                                 */
/* -------------------------------------------------------------------- */
    if( eNodeType == SNT_COLUMN )
    {
        if( field_list == nullptr )
        {
            if( table_name )
                osExpr.Printf(
                    "%s.%s",
                    QuoteIfNecessary(table_name, chColumnQuote).c_str(),
                    QuoteIfNecessary(string_value, chColumnQuote).c_str() );
            else
                osExpr.Printf(
                    "%s",
                    QuoteIfNecessary(string_value, chColumnQuote).c_str() );
        }
        else if( field_index != -1
            && table_index < field_list->table_count
            && table_index > 0 )
        {
            // We deliberately browse through the list starting from the end
            // This is for the case where the FID column exists both as
            // FID and then real_fid_name. We want real_fid_name to be used
            for( int i = field_list->count - 1; i >= 0; i-- )
            {
                if( field_list->table_ids[i] == table_index &&
                    field_list->ids[i] == field_index )
                {
                    osExpr.Printf( "%s.%s",
                                   QuoteIfNecessary(field_list->table_defs[table_index].table_name, chColumnQuote).c_str(),
                                   QuoteIfNecessary(field_list->names[i], chColumnQuote).c_str() );
                    break;
                }
            }
        }
        else if( field_index != -1 )
        {
            // We deliberately browse through the list starting from the end
            // This is for the case where the FID column exists both as
            // FID and then real_fid_name. We want real_fid_name to be used
            for( int i = field_list->count - 1; i >= 0; i-- )
            {
                if( field_list->table_ids[i] == table_index &&
                    field_list->ids[i] == field_index )
                {
                    osExpr.Printf( "%s", QuoteIfNecessary(field_list->names[i], chColumnQuote).c_str() );
                    break;
                }
            }
        }

        if( osExpr.empty() )
        {
            return CPLStrdup(CPLSPrintf("%c%c", chColumnQuote, chColumnQuote));
        }

        // The string is just alphanum and not a reserved SQL keyword,
        // no needs to quote and escape.
        return CPLStrdup(osExpr.c_str());
    }

/* -------------------------------------------------------------------- */
/*      Operation - start by unparsing all the subexpressions.          */
/* -------------------------------------------------------------------- */
    std::vector<char*> apszSubExpr;
    apszSubExpr.reserve(nSubExprCount);
    for( int i = 0; i < nSubExprCount; i++ )
        apszSubExpr.push_back( papoSubExpr[i]->Unparse(field_list, chColumnQuote) );

    osExpr = UnparseOperationFromUnparsedSubExpr(&apszSubExpr[0]);

/* -------------------------------------------------------------------- */
/*      cleanup subexpressions.                                         */
/* -------------------------------------------------------------------- */
    for( int i = 0; i < nSubExprCount; i++ )
        CPLFree( apszSubExpr[i] );

    return CPLStrdup( osExpr.c_str() );
}

/************************************************************************/
/*                  UnparseOperationFromUnparsedSubExpr()               */
/************************************************************************/

CPLString swq_expr_node::UnparseOperationFromUnparsedSubExpr(char** apszSubExpr)
{
    CPLString osExpr;

/* -------------------------------------------------------------------- */
/*      Put things together in a fashion depending on the operator.     */
/* -------------------------------------------------------------------- */
    const swq_operation *poOp =
        swq_op_registrar::GetOperator( nOperation );

    if( poOp == nullptr && nOperation != SWQ_CUSTOM_FUNC )
    {
        CPLAssert( false );
        return osExpr;
    }

    switch( nOperation )
    {
      // Binary infix operators.
      case SWQ_OR:
      case SWQ_AND:
      case SWQ_EQ:
      case SWQ_NE:
      case SWQ_GT:
      case SWQ_LT:
      case SWQ_GE:
      case SWQ_LE:
      case SWQ_LIKE:
      case SWQ_ILIKE:
      case SWQ_ADD:
      case SWQ_SUBTRACT:
      case SWQ_MULTIPLY:
      case SWQ_DIVIDE:
      case SWQ_MODULUS:
        CPLAssert( nSubExprCount >= 2 );
        if( papoSubExpr[0]->eNodeType == SNT_COLUMN ||
            papoSubExpr[0]->eNodeType == SNT_CONSTANT )
        {
            osExpr += apszSubExpr[0];
        }
        else
        {
            osExpr += "(";
            osExpr += apszSubExpr[0];
            osExpr += ")";
        }
        osExpr += " ";
        osExpr += poOp->pszName;
        osExpr += " ";
        if( papoSubExpr[1]->eNodeType == SNT_COLUMN ||
            papoSubExpr[1]->eNodeType == SNT_CONSTANT )
        {
            osExpr += apszSubExpr[1];
        }
        else
        {
            osExpr += "(";
            osExpr += apszSubExpr[1];
            osExpr += ")";
        }
        if( (nOperation == SWQ_LIKE || nOperation == SWQ_ILIKE) && nSubExprCount == 3 )
            osExpr += CPLSPrintf( " ESCAPE (%s)", apszSubExpr[2] );
        break;

      case SWQ_NOT:
        CPLAssert( nSubExprCount == 1 );
        osExpr.Printf( "NOT (%s)", apszSubExpr[0] );
        break;

      case SWQ_ISNULL:
        CPLAssert( nSubExprCount == 1 );
        osExpr.Printf( "%s IS NULL", apszSubExpr[0] );
        break;

      case SWQ_IN:
        osExpr.Printf( "%s IN (", apszSubExpr[0] );
        for( int i = 1; i < nSubExprCount; i++ )
        {
            if( i > 1 )
                osExpr += ",";
            osExpr += "(";
            osExpr += apszSubExpr[i];
            osExpr += ")";
        }
        osExpr += ")";
        break;

      case SWQ_BETWEEN:
        CPLAssert( nSubExprCount == 3 );
        osExpr.Printf( "%s %s (%s) AND (%s)",
                       apszSubExpr[0],
                       poOp->pszName,
                       apszSubExpr[1],
                       apszSubExpr[2] );
        break;

      case SWQ_CAST:
        osExpr = "CAST(";
        for( int i = 0; i < nSubExprCount; i++ )
        {
            if( i == 1 )
                osExpr += " AS ";
            else if( i > 2 )
                osExpr += ", ";

            const int nLen = static_cast<int>(strlen(apszSubExpr[i]));
            if( (i == 1 &&
                (apszSubExpr[i][0] == '\'' &&
                 nLen > 2 && apszSubExpr[i][nLen-1] == '\'')) ||
                (i == 2 && EQUAL(apszSubExpr[1], "'GEOMETRY")) )
            {
                apszSubExpr[i][nLen-1] = '\0';
                osExpr += apszSubExpr[i] + 1;
            }
            else
                osExpr += apszSubExpr[i];

            if( i == 1 && nSubExprCount > 2)
                osExpr += "(";
            else if( i > 1 && i == nSubExprCount - 1 )
                osExpr += ")";
        }
        osExpr += ")";
        break;

      default: // function style.
        if( nOperation != SWQ_CUSTOM_FUNC )
            osExpr.Printf( "%s(", poOp->pszName );
        else
            osExpr.Printf( "%s(", string_value );
        for( int i = 0; i < nSubExprCount; i++ )
        {
            if( i > 0 )
                osExpr += ",";
            osExpr += "(";
            osExpr += apszSubExpr[i];
            osExpr += ")";
        }
        osExpr += ")";
        break;
    }

    return osExpr;
}

/************************************************************************/
/*                               Clone()                                */
/************************************************************************/

swq_expr_node *swq_expr_node::Clone()
{
    swq_expr_node* poRetNode = new swq_expr_node();

    poRetNode->eNodeType = eNodeType;
    poRetNode->field_type = field_type;
    if( eNodeType == SNT_OPERATION )
    {
        poRetNode->nOperation = nOperation;
        poRetNode->nSubExprCount = nSubExprCount;
        poRetNode->papoSubExpr = static_cast<swq_expr_node **>(
                CPLMalloc( sizeof(void*) * nSubExprCount ));
        for( int i = 0; i < nSubExprCount; i++ )
            poRetNode->papoSubExpr[i] = papoSubExpr[i]->Clone();
    }
    else if( eNodeType == SNT_COLUMN )
    {
        poRetNode->field_index = field_index;
        poRetNode->table_index = table_index;
        poRetNode->table_name = table_name ? CPLStrdup(table_name) : nullptr;
    }
    else if( eNodeType == SNT_CONSTANT )
    {
        poRetNode->is_null = is_null;
        poRetNode->int_value = int_value;
        poRetNode->float_value = float_value;
        if( geometry_value )
            poRetNode->geometry_value = geometry_value->clone();
        else
            poRetNode->geometry_value = nullptr;
    }
    poRetNode->string_value = string_value ? CPLStrdup(string_value) : nullptr;
    return poRetNode;
}

/************************************************************************/
/*                              Evaluate()                              */
/************************************************************************/

swq_expr_node *swq_expr_node::Evaluate( swq_field_fetcher pfnFetcher,
                                        void *pRecord )

{
    return Evaluate(pfnFetcher, pRecord, 0);
}

swq_expr_node *swq_expr_node::Evaluate( swq_field_fetcher pfnFetcher,
                                        void *pRecord, int nRecLevel )

{
    swq_expr_node *poRetNode = nullptr;
    if( nRecLevel == 32 )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Too many recursion levels in expression");
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Duplicate ourselves if we are already a constant.               */
/* -------------------------------------------------------------------- */
    if( eNodeType == SNT_CONSTANT )
    {
        return Clone();
    }

/* -------------------------------------------------------------------- */
/*      If this is a field value from a record, fetch and return it.    */
/* -------------------------------------------------------------------- */
    if( eNodeType == SNT_COLUMN )
    {
        return pfnFetcher( this, pRecord );
    }

/* -------------------------------------------------------------------- */
/*      This is an operation, collect the arguments keeping track of    */
/*      which we will need to free.                                     */
/* -------------------------------------------------------------------- */
    std::vector<swq_expr_node*> apoValues;
    std::vector<int> anValueNeedsFree;
    bool bError = false;
    apoValues.reserve(nSubExprCount);
    for( int i = 0; i < nSubExprCount && !bError; i++ )
    {
        if( papoSubExpr[i]->eNodeType == SNT_CONSTANT )
        {
            // avoid duplication.
            apoValues.push_back( papoSubExpr[i] );
            anValueNeedsFree.push_back( FALSE );
        }
        else
        {
            swq_expr_node* poSubExprVal =
                papoSubExpr[i]->Evaluate(pfnFetcher, pRecord, nRecLevel + 1);
            if( poSubExprVal == nullptr )
                bError = true;
            else
            {
                apoValues.push_back(poSubExprVal);
                anValueNeedsFree.push_back( TRUE );
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Fetch the operator definition and function.                     */
/* -------------------------------------------------------------------- */
    if( !bError )
    {
        const swq_operation *poOp =
            swq_op_registrar::GetOperator( nOperation );
        if( poOp == nullptr )
        {
            if( nOperation == SWQ_CUSTOM_FUNC )
                CPLError( CE_Failure, CPLE_AppDefined,
                        "Evaluate(): Unable to find definition for operator %s.",
                        string_value );
            else
                CPLError(
                    CE_Failure, CPLE_AppDefined,
                    "Evaluate(): Unable to find definition for operator %d.",
                    nOperation );
            poRetNode = nullptr;
        }
        else
            poRetNode = poOp->pfnEvaluator( this, &(apoValues[0]) );
    }

/* -------------------------------------------------------------------- */
/*      Cleanup                                                         */
/* -------------------------------------------------------------------- */
    for( int i = 0; i < static_cast<int>(apoValues.size()); i++ )
    {
        if( anValueNeedsFree[i] )
            delete apoValues[i];
    }

    // cppcheck-suppress returnDanglingLifetime
    return poRetNode;
}

/************************************************************************/
/*                      ReplaceBetweenByGEAndLERecurse()                */
/************************************************************************/

void swq_expr_node::ReplaceBetweenByGEAndLERecurse()
{
    if( eNodeType != SNT_OPERATION )
        return;

    if( nOperation != SWQ_BETWEEN )
    {
        for( int i = 0; i < nSubExprCount; i++ )
            papoSubExpr[i]->ReplaceBetweenByGEAndLERecurse();
        return;
    }

    if( nSubExprCount != 3 )
        return;

    swq_expr_node* poExpr0 = papoSubExpr[0];
    swq_expr_node* poExpr1 = papoSubExpr[1];
    swq_expr_node* poExpr2 = papoSubExpr[2];

    nSubExprCount = 2;
    nOperation = SWQ_AND;
    papoSubExpr[0] = new swq_expr_node(SWQ_GE);
    papoSubExpr[0]->PushSubExpression(poExpr0);
    papoSubExpr[0]->PushSubExpression(poExpr1);
    papoSubExpr[1] = new swq_expr_node(SWQ_LE);
    papoSubExpr[1]->PushSubExpression(poExpr0->Clone());
    papoSubExpr[1]->PushSubExpression(poExpr2);
}

#endif  // #ifndef DOXYGEN_SKIP
