/******************************************************************************
 *
 * Component: OGDI Driver Support Library
 * Purpose: Generic SQL WHERE Expression Implementation.
 * Author: Frank Warmerdam <warmerdam@pobox.com>
 *
 ******************************************************************************
 * Copyright (C) 2001 Information Interoperability Institute (3i)
 * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies, that
 * both the copyright notice and this permission notice appear in
 * supporting documentation, and that the name of 3i not be used
 * in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  3i makes no
 * representations about the suitability of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
 ****************************************************************************/

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

#include <cassert>
#include <cctype>
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <ctime>

#include <algorithm>
#include <limits>
#include <queue>
#include <string>

#include "cpl_error.h"
#include "cpl_multiproc.h"
#include "cpl_time.h"
#include "swq_parser.hpp"

CPL_CVSID("$Id$")

#define YYSTYPE swq_expr_node *

/************************************************************************/
/*                               swqlex()                               */
/************************************************************************/

void swqerror( swq_parse_context *context, const char *msg )
{
    CPLString osMsg;
    osMsg.Printf( "SQL Expression Parsing Error: %s. Occurred around :\n",
                  msg );

    int n = static_cast<int>(context->pszLastValid - context->pszInput);

    for( int i = std::max(0, n - 40);
         i < n + 40 && context->pszInput[i] != '\0';
         i++ )
        osMsg += context->pszInput[i];
    osMsg += "\n";
    for( int i = 0; i < std::min(n, 40); i++ )
        osMsg += " ";
    osMsg += "^";

    CPLError( CE_Failure, CPLE_AppDefined, "%s", osMsg.c_str() );
}

/************************************************************************/
/*                               swqlex()                               */
/*                                                                      */
/*      Read back a token from the input.                               */
/************************************************************************/

int swqlex( YYSTYPE *ppNode, swq_parse_context *context )
{
    const char *pszInput = context->pszNext;

    *ppNode = nullptr;

/* -------------------------------------------------------------------- */
/*      Do we have a start symbol to return?                            */
/* -------------------------------------------------------------------- */
    if( context->nStartToken != 0 )
    {
        int nRet = context->nStartToken;
        context->nStartToken = 0;
        return nRet;
    }

/* -------------------------------------------------------------------- */
/*      Skip white space.                                               */
/* -------------------------------------------------------------------- */
    while( *pszInput == ' ' || *pszInput == '\t'
           || *pszInput == 10 || *pszInput == 13 )
        pszInput++;

    context->pszLastValid = pszInput;

    if( *pszInput == '\0' )
    {
        context->pszNext = pszInput;
        return EOF;
    }

/* -------------------------------------------------------------------- */
/*      Handle string constants.                                        */
/* -------------------------------------------------------------------- */
    if( *pszInput == '"' || *pszInput == '\'' )
    {
        char chQuote = *pszInput;
        bool bFoundEndQuote = false;

        int nRet = *pszInput == '"' ? SWQT_IDENTIFIER : SWQT_STRING;

        pszInput++;

        char *token = static_cast<char *>(CPLMalloc(strlen(pszInput) + 1));
        int i_token = 0;

        while( *pszInput != '\0' )
        {
            if( chQuote == '"' && *pszInput == '\\' && pszInput[1] == '"' )
                pszInput++;
            else if( chQuote == '\'' && *pszInput == '\\' &&
                     pszInput[1] == '\'' )
                pszInput++;
            else if( chQuote == '\'' && *pszInput == '\'' &&
                     pszInput[1] == '\'' )
                pszInput++;
            else if( *pszInput == chQuote )
            {
                pszInput++;
                bFoundEndQuote = true;
                break;
            }

            token[i_token++] = *(pszInput++);
        }
        token[i_token] = '\0';

        if( !bFoundEndQuote )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Did not find end-of-string character");
            CPLFree( token );
            return 0;
        }

        *ppNode = new swq_expr_node( token );
        CPLFree( token );

        context->pszNext = pszInput;

        return nRet;
    }

/* -------------------------------------------------------------------- */
/*      Handle numbers.                                                 */
/* -------------------------------------------------------------------- */
    else if( *pszInput >= '0' && *pszInput <= '9' )
    {
        CPLString osToken;
        const char *pszNext = pszInput + 1;

        osToken += *pszInput;

        // collect non-decimal part of number
        while( *pszNext >= '0' && *pszNext <= '9' )
            osToken += *(pszNext++);

        // collect decimal places.
        if( *pszNext == '.' )
        {
            osToken += *(pszNext++);
            while( *pszNext >= '0' && *pszNext <= '9' )
                osToken += *(pszNext++);
        }

        // collect exponent
        if( *pszNext == 'e' || *pszNext == 'E' )
        {
            osToken += *(pszNext++);
            if( *pszNext == '-' || *pszNext == '+' )
                osToken += *(pszNext++);
            while( *pszNext >= '0' && *pszNext <= '9' )
                osToken += *(pszNext++);
        }

        context->pszNext = pszNext;

        if( strstr(osToken, ".")
            || strstr(osToken, "e")
            || strstr(osToken, "E") )
        {
            *ppNode = new swq_expr_node( CPLAtof(osToken) );
            return SWQT_FLOAT_NUMBER;
        }
        else
        {
            if( osToken.size() > 19 ||
                (osToken.size() >= 19 && osToken > "9223372036854775807") )
            {
                *ppNode = new swq_expr_node( CPLAtof(osToken) );
                if( osToken == "9223372036854775808" )
                    (*ppNode)->string_value = CPLStrdup(osToken);
                return SWQT_FLOAT_NUMBER;
            }

            GIntBig nVal = CPLAtoGIntBig(osToken);
            if( CPL_INT64_FITS_ON_INT32(nVal) )
                *ppNode = new swq_expr_node( static_cast<int>(nVal) );
            else
                *ppNode = new swq_expr_node( nVal );
            return SWQT_INTEGER_NUMBER;
        }
    }

/* -------------------------------------------------------------------- */
/*      Handle alpha-numerics.                                          */
/* -------------------------------------------------------------------- */
    else if( isalnum( *pszInput ) )
    {
        int nReturn = SWQT_IDENTIFIER;
        CPLString osToken;
        const char *pszNext = pszInput + 1;

        osToken += *pszInput;

        // collect text characters
        while( isalnum( *pszNext ) || *pszNext == '_'
               || static_cast<unsigned char>(*pszNext) > 127 )
            osToken += *(pszNext++);

        context->pszNext = pszNext;

        if( EQUAL(osToken, "IN") )
            nReturn = SWQT_IN;
        else if( EQUAL(osToken, "LIKE") )
            nReturn = SWQT_LIKE;
        else if( EQUAL(osToken, "ILIKE") )
            nReturn = SWQT_ILIKE;
        else if( EQUAL(osToken, "ESCAPE") )
            nReturn = SWQT_ESCAPE;
        else if( EQUAL(osToken, "NULL") )
            nReturn = SWQT_NULL;
        else if( EQUAL(osToken, "IS") )
            nReturn = SWQT_IS;
        else if( EQUAL(osToken, "NOT") )
            nReturn = SWQT_NOT;
        else if( EQUAL(osToken, "AND") )
            nReturn = SWQT_AND;
        else if( EQUAL(osToken, "OR") )
            nReturn = SWQT_OR;
        else if( EQUAL(osToken, "BETWEEN") )
            nReturn = SWQT_BETWEEN;
        else if( EQUAL(osToken, "SELECT") )
            nReturn = SWQT_SELECT;
        else if( EQUAL(osToken, "LEFT") )
            nReturn = SWQT_LEFT;
        else if( EQUAL(osToken, "JOIN") )
            nReturn = SWQT_JOIN;
        else if( EQUAL(osToken, "WHERE") )
            nReturn = SWQT_WHERE;
        else if( EQUAL(osToken, "ON") )
            nReturn = SWQT_ON;
        else if( EQUAL(osToken, "ORDER") )
            nReturn = SWQT_ORDER;
        else if( EQUAL(osToken, "BY") )
            nReturn = SWQT_BY;
        else if( EQUAL(osToken, "FROM") )
            nReturn = SWQT_FROM;
        else if( EQUAL(osToken, "AS") )
            nReturn = SWQT_AS;
        else if( EQUAL(osToken, "ASC") )
            nReturn = SWQT_ASC;
        else if( EQUAL(osToken, "DESC") )
            nReturn = SWQT_DESC;
        else if( EQUAL(osToken, "DISTINCT") )
            nReturn = SWQT_DISTINCT;
        else if( EQUAL(osToken, "CAST") )
            nReturn = SWQT_CAST;
        else if( EQUAL(osToken, "UNION") )
            nReturn = SWQT_UNION;
        else if( EQUAL(osToken, "ALL") )
            nReturn = SWQT_ALL;
        else if( EQUAL(osToken, "LIMIT") )
            nReturn = SWQT_LIMIT;
        else if( EQUAL(osToken, "OFFSET") )
            nReturn = SWQT_OFFSET;

        // Unhandled by OGR SQL.
        else if( EQUAL(osToken, "LIMIT") ||
                 EQUAL(osToken, "OUTER") ||
                 EQUAL(osToken, "INNER") )
            nReturn = SWQT_RESERVED_KEYWORD;

        else
        {
            *ppNode = new swq_expr_node( osToken );
            nReturn = SWQT_IDENTIFIER;
        }

        return nReturn;
    }

/* -------------------------------------------------------------------- */
/*      Handle special tokens.                                          */
/* -------------------------------------------------------------------- */
    else
    {
        context->pszNext = pszInput+1;
        return *pszInput;
    }
}

/************************************************************************/
/*                        swq_select_summarize()                        */
/************************************************************************/

const char *
swq_select_summarize( swq_select *select_info,
                      int dest_column, const char *value )

{
    swq_col_def *def = select_info->column_defs + dest_column;

/* -------------------------------------------------------------------- */
/*      Do various checking.                                            */
/* -------------------------------------------------------------------- */
    if( select_info->query_mode == SWQM_RECORDSET )
        return "swq_select_summarize() called on non-summary query.";

    if( dest_column < 0 || dest_column >= select_info->result_columns )
        return "dest_column out of range in swq_select_summarize().";

    if( def->col_func == SWQCF_NONE && !def->distinct_flag )
        return nullptr;

    if( select_info->query_mode == SWQM_DISTINCT_LIST &&
        select_info->order_specs > 0 )
    {
        if( select_info->order_specs > 1 )
            return "Can't ORDER BY a DISTINCT list by more than one key.";

        if( select_info->order_defs[0].field_index !=
            select_info->column_defs[0].field_index )
            return "Only selected DISTINCT field can be used for ORDER BY.";
    }

/* -------------------------------------------------------------------- */
/*      Create the summary information if this is the first row         */
/*      being processed.                                                */
/* -------------------------------------------------------------------- */
    if( select_info->column_summary.empty() )
    {
        select_info->column_summary.resize(select_info->result_columns);
        for( int i = 0; i < select_info->result_columns; i++ )
        {
            if( def->distinct_flag )
            {
                swq_summary::Comparator oComparator;
                if( select_info->order_specs > 0 )
                {
                    CPLAssert( select_info->order_specs ==1 );
                    CPLAssert( select_info->result_columns == 1 );
                    oComparator.bSortAsc =
                        CPL_TO_BOOL(select_info->order_defs[0].ascending_flag);
                }
                if( select_info->column_defs[i].field_type == SWQ_INTEGER ||
-                   select_info->column_defs[i].field_type == SWQ_INTEGER64 )
                {
                    oComparator.eType = SWQ_INTEGER64;
                }
                else if( select_info->column_defs[i].field_type == SWQ_FLOAT )
                {
                    oComparator.eType = SWQ_FLOAT;
                }
                else
                {
                    oComparator.eType = SWQ_STRING;
                }
                select_info->column_summary[i].oSetDistinctValues =
                    std::set<CPLString, swq_summary::Comparator>(oComparator);
            }
            select_info->column_summary[i].min =
                std::numeric_limits<double>::infinity();
            select_info->column_summary[i].max =
                -std::numeric_limits<double>::infinity();
            select_info->column_summary[i].osMin = "9999/99/99 99:99:99";
            select_info->column_summary[i].osMax = "0000/00/00 00:00:00";
        }
        assert( !select_info->column_summary.empty() );
    }

/* -------------------------------------------------------------------- */
/*      If distinct processing is on, process that now.                 */
/* -------------------------------------------------------------------- */
    swq_summary& summary = select_info->column_summary[dest_column];

    if( def->distinct_flag )
    {
        if( value == nullptr )
            value = SZ_OGR_NULL;
        try
        {
            if( summary.oSetDistinctValues.find(value) ==
                    summary.oSetDistinctValues.end() )
            {
                summary.oSetDistinctValues.insert(value);
                if( select_info->order_specs == 0 )
                {
                    // If not sorted, keep values in their original order
                    summary.oVectorDistinctValues.emplace_back(value);
                }
                summary.count ++;
            }
        }
        catch( std::bad_alloc& )
        {
            return "Out of memory";
        }

        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Process various options.                                        */
/* -------------------------------------------------------------------- */

    switch( def->col_func )
    {
      case SWQCF_MIN:
        if( value != nullptr && value[0] != '\0' )
        {
            if( def->field_type == SWQ_DATE ||
                def->field_type == SWQ_TIME ||
                def->field_type == SWQ_TIMESTAMP )
            {
                if( strcmp( value, summary.osMin ) < 0 )
                {
                    summary.osMin = value;
                }
            }
            else
            {
                double df_val = CPLAtof(value);
                if( df_val < summary.min )
                    summary.min = df_val;
            }
            summary.count++;
        }
        break;
      case SWQCF_MAX:
        if( value != nullptr && value[0] != '\0' )
        {
            if( def->field_type == SWQ_DATE ||
                def->field_type == SWQ_TIME ||
                def->field_type == SWQ_TIMESTAMP )
            {
                if( strcmp( value, summary.osMax ) > 0 )
                {
                    summary.osMax = value;
                }
            }
            else
            {
                double df_val = CPLAtof(value);
                if( df_val > summary.max )
                    summary.max = df_val;
            }
            summary.count++;
        }
        break;
      case SWQCF_AVG:
      case SWQCF_SUM:
        if( value != nullptr && value[0] != '\0' )
        {
            if( def->field_type == SWQ_DATE ||
                def->field_type == SWQ_TIME ||
                def->field_type == SWQ_TIMESTAMP )
            {
                OGRField sField;
                if( OGRParseDate( value, &sField, 0 ) )
                {
                    struct tm brokendowntime;
                    brokendowntime.tm_year = sField.Date.Year - 1900;
                    brokendowntime.tm_mon = sField.Date.Month - 1;
                    brokendowntime.tm_mday = sField.Date.Day;
                    brokendowntime.tm_hour = sField.Date.Hour;
                    brokendowntime.tm_min = sField.Date.Minute;
                    brokendowntime.tm_sec =
                        static_cast<int>(sField.Date.Second);
                    summary.count++;
                    summary.sum += CPLYMDHMSToUnixTime(&brokendowntime);
                    summary.sum += fmod(static_cast<double>(
                                                    sField.Date.Second), 1.0);
                }
            }
            else
            {
                summary.count++;
                summary.sum += CPLAtof(value);
            }
        }
        break;

      case SWQCF_COUNT:
        if( value != nullptr )
            summary.count++;
        break;

      case SWQCF_NONE:
        break;

      case SWQCF_CUSTOM:
        return "swq_select_summarize() called on custom field function.";

      default:
        return "swq_select_summarize() - unexpected col_func";
    }

    return nullptr;
}
/************************************************************************/
/*                      sort comparison functions.                      */
/************************************************************************/

static bool Compare (swq_field_type eType,
                     const CPLString& a,
                     const CPLString& b)
{
    if( a == SZ_OGR_NULL )
        return b != SZ_OGR_NULL;
    else if( b == SZ_OGR_NULL )
        return false;
    else
    {
        if( eType == SWQ_INTEGER64 )
            return CPLAtoGIntBig(a) < CPLAtoGIntBig(b);
        else if( eType == SWQ_FLOAT )
            return CPLAtof(a) < CPLAtof(b);
        else if( eType == SWQ_STRING )
            return a < b;
        else
        {
            CPLAssert( false );
            return false;
        }
    }
}

#ifndef DOXYGEN_SKIP
bool swq_summary::Comparator::operator() (const CPLString& a,
                                          const CPLString& b) const
{
    if( bSortAsc )
    {
        return Compare(eType, a, b);
    }
    else
    {
        return Compare(eType, b, a);
    }
}
#endif

/************************************************************************/
/*                         swq_identify_field()                         */
/************************************************************************/
int swq_identify_field_internal( const char* table_name,
                                 const char *field_token,
                                 swq_field_list *field_list,
                                 swq_field_type *this_type, int *table_id,
                                 int bOneMoreTimeOK );

int swq_identify_field( const char* table_name, const char *field_token,
                                 swq_field_list *field_list,
                                 swq_field_type *this_type, int *table_id )

{
    return swq_identify_field_internal(table_name, field_token, field_list,
                                       this_type, table_id, TRUE);
}

int swq_identify_field_internal( const char* table_name,
                                 const char *field_token,
                                 swq_field_list *field_list,
                                 swq_field_type *this_type, int *table_id,
                                 int bOneMoreTimeOK )

{
    if( table_name == nullptr )
        table_name = "";

    int tables_enabled;

    if( field_list->table_count > 0 && field_list->table_ids != nullptr )
        tables_enabled = TRUE;
    else
        tables_enabled = FALSE;

/* -------------------------------------------------------------------- */
/*      Search for matching field.                                      */
/* -------------------------------------------------------------------- */
    for( int pass = 0; pass < 2; ++pass )
    {
        for( int i = 0; i < field_list->count; i++ )
        {
            if( (pass == 0 && strcmp( field_list->names[i], field_token ) != 0) ||
                (pass == 1 && !EQUAL( field_list->names[i], field_token ) ) )
            {
                continue;
            }

            int t_id = 0;

            // Do the table specifications match?/
            if( tables_enabled )
            {
                t_id = field_list->table_ids[i];
                if( table_name[0] != '\0'
                    && !EQUAL(table_name, field_list->table_defs[t_id].table_alias))
                    continue;

                // if( t_id != 0 && table_name[0] == '\0' )
                //     continue;
            }
            else if( table_name[0] != '\0' )
                break;

            // We have a match, return various information.
            if( this_type != nullptr )
            {
                if( field_list->types != nullptr )
                    *this_type = field_list->types[i];
                else
                    *this_type = SWQ_OTHER;
            }

            if( table_id != nullptr )
                *table_id = t_id;

            if( field_list->ids == nullptr )
                return i;
            else
                return field_list->ids[i];
        }
    }

/* -------------------------------------------------------------------- */
/*      When there is no ambiguity, try to accept quoting errors...     */
/* -------------------------------------------------------------------- */
    if( bOneMoreTimeOK &&
        !CPLTestBool(CPLGetConfigOption("OGR_SQL_STRICT", "FALSE")) )
    {
        if( table_name[0] )
        {
            CPLString osAggregatedName(
                CPLSPrintf("%s.%s", table_name, field_token));

            // Check there's no table called table_name, or a field called with
            // the aggregated name.
            int i = 0;  // Used after for.
            for( ; i < field_list->count; i++ )
            {
                if( tables_enabled )
                {
                    int t_id = field_list->table_ids[i];
                    if( EQUAL(table_name,
                              field_list->table_defs[t_id].table_alias) )
                        break;
                }
            }
            if( i == field_list->count )
            {
                int ret = swq_identify_field_internal( nullptr,
                                            osAggregatedName,
                                            field_list,
                                            this_type, table_id, FALSE );
                if( ret >= 0 )
                {
                    CPLError(CE_Warning, CPLE_AppDefined,
                             "Passed field name %s.%s should have been "
                             "surrounded by double quotes. "
                             "Accepted since there is no ambiguity...",
                             table_name, field_token);
                }
                return ret;
            }
        }
        else
        {
            // If the fieldname is a.b (and there's no . in b), then
            // it might be an error in providing it as being quoted where it
            // should not have been quoted.
            const char* pszDot = strchr(field_token, '.');
            if( pszDot && strchr(pszDot+1, '.') == nullptr )
            {
                CPLString osTableName(field_token);
                osTableName.resize(pszDot - field_token);
                CPLString osFieldName(pszDot + 1);

                int ret = swq_identify_field_internal( osTableName,
                                            osFieldName,
                                            field_list,
                                            this_type, table_id, FALSE );
                if( ret >= 0 )
                {
                    CPLError(CE_Warning, CPLE_AppDefined,
                            "Passed field name %s should NOT have been "
                             "surrounded by double quotes. "
                            "Accepted since there is no ambiguity...",
                            field_token);
                }
                return ret;
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      No match, return failure.                                       */
/* -------------------------------------------------------------------- */
    if( this_type != nullptr )
        *this_type = SWQ_OTHER;

    if( table_id != nullptr )
        *table_id = 0;

    return -1;
}

/************************************************************************/
/*                          swq_expr_compile()                          */
/************************************************************************/

CPLErr swq_expr_compile( const char *where_clause,
                         int field_count,
                         char **field_names,
                         swq_field_type *field_types,
                         int bCheck,
                         swq_custom_func_registrar* poCustomFuncRegistrar,
                         swq_expr_node **expr_out )

{
    swq_field_list field_list;

    field_list.count = field_count;
    field_list.names = field_names;
    field_list.types = field_types;
    field_list.table_ids = nullptr;
    field_list.ids = nullptr;

    field_list.table_count = 0;
    field_list.table_defs = nullptr;

    return swq_expr_compile2( where_clause, &field_list,
                              bCheck, poCustomFuncRegistrar, expr_out );
}

/************************************************************************/
/*                       swq_fixup_expression()                         */
/************************************************************************/

static void swq_fixup_expression(swq_expr_node* node)
{
    std::queue<swq_expr_node*> nodes;
    nodes.push(node);
    while( !nodes.empty() )
    {
        node = nodes.front();
        nodes.pop();
        if( node->eNodeType == SNT_OPERATION )
        {
            const swq_op eOp = node->nOperation;
            if( (eOp == SWQ_OR || eOp == SWQ_AND) && node->nSubExprCount > 2 )
            {
                std::vector<swq_expr_node*> exprs;
                for( int i = 0; i < node->nSubExprCount; i++ )
                {
                    swq_fixup_expression(node->papoSubExpr[i]);
                    exprs.push_back(node->papoSubExpr[i]);
                }
                node->nSubExprCount = 0;
                CPLFree( node->papoSubExpr );
                node->papoSubExpr = nullptr;

                while(exprs.size() > 2)
                {
                    std::vector<swq_expr_node*> new_exprs;
                    for(size_t i = 0; i < exprs.size(); i++ )
                    {
                        if( i + 1 < exprs.size() )
                        {
                            auto cur_expr = new swq_expr_node( eOp );
                            cur_expr->field_type = SWQ_BOOLEAN;
                            cur_expr->PushSubExpression(exprs[i]);
                            cur_expr->PushSubExpression(exprs[i+1]);
                            i++;
                            new_exprs.push_back(cur_expr);
                        }
                        else
                        {
                            new_exprs.push_back(exprs[i]);
                        }
                    }
                    exprs = std::move(new_exprs);
                }
                CPLAssert(exprs.size() == 2);
                node->PushSubExpression(exprs[0]);
                node->PushSubExpression(exprs[1]);
            }
            else
            {
                for( int i = 0; i < node->nSubExprCount; i++ )
                {
                    nodes.push(node->papoSubExpr[i]);
                }
            }
        }
    }
}

/************************************************************************/
/*                       swq_fixup_expression()                         */
/************************************************************************/

void swq_fixup(swq_parse_context* psParseContext)
{
    if( psParseContext->poRoot )
    {
        swq_fixup_expression(psParseContext->poRoot);
    }
    auto psSelect = psParseContext->poCurSelect;
    while( psSelect )
    {
        if( psSelect->where_expr )
        {
            swq_fixup_expression(psSelect->where_expr);
        }
        psSelect = psSelect->poOtherSelect;
    }
}

/************************************************************************/
/*                       swq_create_and_or_or()                         */
/************************************************************************/

swq_expr_node* swq_create_and_or_or(swq_op op, swq_expr_node* left, swq_expr_node* right)
{
    auto poNode = new swq_expr_node( op );
    poNode->field_type = SWQ_BOOLEAN;

    if( left->eNodeType == SNT_OPERATION &&
        left->nOperation == op  )
    {
        // Temporary non-binary formulation
        if( right->eNodeType == SNT_OPERATION &&
            right->nOperation == op  )
        {
            poNode->nSubExprCount = left->nSubExprCount + right->nSubExprCount;
            poNode->papoSubExpr = static_cast<swq_expr_node **>(
                CPLRealloc( left->papoSubExpr, sizeof(swq_expr_node*) * poNode->nSubExprCount ));
            memcpy(poNode->papoSubExpr + left->nSubExprCount,
                   right->papoSubExpr,
                   right->nSubExprCount * sizeof(swq_expr_node*));

            right->nSubExprCount = 0;
            right->papoSubExpr = nullptr;
            delete right;
        }
        else
        {
            poNode->nSubExprCount = left->nSubExprCount;
            poNode->papoSubExpr = left->papoSubExpr;
            poNode->PushSubExpression( right );
        }

        left->nSubExprCount = 0;
        left->papoSubExpr = nullptr;
        delete left;
    }
    else if( right->eNodeType == SNT_OPERATION &&
             right->nOperation == op  )
    {
        // Temporary non-binary formulation
        poNode->nSubExprCount = right->nSubExprCount;
        poNode->papoSubExpr = right->papoSubExpr;
        poNode->PushSubExpression( left );

        right->nSubExprCount = 0;
        right->papoSubExpr = nullptr;
        delete right;
    }
    else
    {
        poNode->PushSubExpression( left );
        poNode->PushSubExpression( right );
    }

    return poNode;
}

/************************************************************************/
/*                         swq_expr_compile2()                          */
/************************************************************************/

CPLErr swq_expr_compile2( const char *where_clause,
                          swq_field_list *field_list,
                          int bCheck,
                          swq_custom_func_registrar* poCustomFuncRegistrar,
                          swq_expr_node **expr_out )

{
    swq_parse_context context;

    context.pszInput = where_clause;
    context.pszNext = where_clause;
    context.pszLastValid = where_clause;
    context.nStartToken = SWQT_VALUE_START;
    context.bAcceptCustomFuncs = poCustomFuncRegistrar != nullptr;

    if( swqparse( &context ) == 0 &&
        bCheck &&
        context.poRoot->Check( field_list, FALSE, FALSE,
                               poCustomFuncRegistrar ) != SWQ_ERROR )
    {
        *expr_out = context.poRoot;

        return CE_None;
    }
    else
    {
        delete context.poRoot;
        *expr_out = nullptr;
        return CE_Failure;
    }
}

/************************************************************************/
/*                        swq_is_reserved_keyword()                     */
/************************************************************************/

static const char* const apszSQLReservedKeywords[] = {
    "OR",
    "AND",
    "NOT",
    "LIKE",
    "IS",
    "NULL",
    "IN",
    "BETWEEN",
    "CAST",
    "DISTINCT",
    "ESCAPE",
    "SELECT",
    "LEFT",
    "JOIN",
    "WHERE",
    "ON",
    "ORDER",
    "BY",
    "FROM",
    "AS",
    "ASC",
    "DESC",
    "UNION",
    "ALL"
};

int swq_is_reserved_keyword(const char* pszStr)
{
    for( const auto& pszKeyword: apszSQLReservedKeywords )
    {
        if( EQUAL(pszStr, pszKeyword) )
            return TRUE;
    }
    return FALSE;
}

/************************************************************************/
/*                          SWQFieldTypeToString()                      */
/************************************************************************/

const char* SWQFieldTypeToString( swq_field_type field_type )
{
    switch( field_type )
    {
        case SWQ_INTEGER:   return "integer";
        case SWQ_INTEGER64: return "bigint";
        case SWQ_FLOAT:     return "float";
        case SWQ_STRING:    return "string";
        case SWQ_BOOLEAN:   return "boolean";
        case SWQ_DATE:      return "date";
        case SWQ_TIME:      return "time";
        case SWQ_TIMESTAMP: return "timestamp";
        case SWQ_GEOMETRY:  return "geometry";
        case SWQ_NULL:      return "null";
        default: return "unknown";
    }
}
