/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Implementation of simple SQL WHERE style attributes queries
 *           for OGRFeatures.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2001, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2008-2014, Even Rouault <even dot rouault at spatialys.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

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

#include <cstddef>
#include <algorithm>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_string.h"
#include "ogr_attrind.h"
#include "ogr_core.h"
#include "ogr_p.h"
#include "ogrsf_frmts.h"

//! @cond Doxygen_Suppress

/************************************************************************/
/*     Support for special attributes (feature query and selection)     */
/************************************************************************/
extern const swq_field_type SpecialFieldTypes[SPECIAL_FIELD_COUNT];

const char *const SpecialFieldNames[SPECIAL_FIELD_COUNT] = {
    "FID", "OGR_GEOMETRY", "OGR_STYLE", "OGR_GEOM_WKT", "OGR_GEOM_AREA"};
const swq_field_type SpecialFieldTypes[SPECIAL_FIELD_COUNT] = {
    SWQ_INTEGER, SWQ_STRING, SWQ_STRING, SWQ_STRING, SWQ_FLOAT};

/************************************************************************/
/*                          OGRFeatureQuery()                           */
/************************************************************************/

OGRFeatureQuery::OGRFeatureQuery()
    : poTargetDefn(nullptr), pSWQExpr(nullptr),
      m_psContext(new swq_evaluation_context())
{
}

/************************************************************************/
/*                          ~OGRFeatureQuery()                          */
/************************************************************************/

OGRFeatureQuery::~OGRFeatureQuery()

{
    delete m_psContext;
    delete static_cast<swq_expr_node *>(pSWQExpr);
}

/************************************************************************/
/*                             Compile()                                */
/************************************************************************/

OGRErr
OGRFeatureQuery::Compile(OGRLayer *poLayer, const char *pszExpression,
                         int bCheck,
                         swq_custom_func_registrar *poCustomFuncRegistrar)

{
    if (poLayer->TestCapability(OLCStringsAsUTF8))
        m_psContext->bUTF8Strings = true;
    return Compile(poLayer, poLayer->GetLayerDefn(), pszExpression, bCheck,
                   poCustomFuncRegistrar);
}

/************************************************************************/
/*                             Compile()                                */
/************************************************************************/

OGRErr
OGRFeatureQuery::Compile(OGRFeatureDefn *poDefn, const char *pszExpression,
                         int bCheck,
                         swq_custom_func_registrar *poCustomFuncRegistrar)

{
    return Compile(nullptr, poDefn, pszExpression, bCheck,
                   poCustomFuncRegistrar);
}

/************************************************************************/
/*                             Compile()                                */
/************************************************************************/

OGRErr
OGRFeatureQuery::Compile(OGRLayer *poLayer, OGRFeatureDefn *poDefn,
                         const char *pszExpression, int bCheck,
                         swq_custom_func_registrar *poCustomFuncRegistrar)
{
    // Clear any existing expression.
    if (pSWQExpr != nullptr)
    {
        delete static_cast<swq_expr_node *>(pSWQExpr);
        pSWQExpr = nullptr;
    }

    const char *pszFIDColumn = nullptr;
    bool bMustAddFID = false;
    if (poLayer != nullptr)
    {
        pszFIDColumn = poLayer->GetFIDColumn();
        if (pszFIDColumn != nullptr)
        {
            if (!EQUAL(pszFIDColumn, "") && !EQUAL(pszFIDColumn, "FID"))
            {
                bMustAddFID = true;
            }
        }
    }

    // Build list of fields.
    const int nFieldCount = poDefn->GetFieldCount() + SPECIAL_FIELD_COUNT +
                            poDefn->GetGeomFieldCount() + (bMustAddFID ? 1 : 0);

    char **papszFieldNames =
        static_cast<char **>(CPLMalloc(sizeof(char *) * nFieldCount));
    swq_field_type *paeFieldTypes = static_cast<swq_field_type *>(
        CPLMalloc(sizeof(swq_field_type) * nFieldCount));

    for (int iField = 0; iField < poDefn->GetFieldCount(); iField++)
    {
        OGRFieldDefn *poField = poDefn->GetFieldDefn(iField);
        if (!poField)
        {
            CPLAssert(0);
            break;
        }

        papszFieldNames[iField] = const_cast<char *>(poField->GetNameRef());

        switch (poField->GetType())
        {
            case OFTInteger:
            {
                if (poField->GetSubType() == OFSTBoolean)
                    paeFieldTypes[iField] = SWQ_BOOLEAN;
                else
                    paeFieldTypes[iField] = SWQ_INTEGER;
                break;
            }

            case OFTInteger64:
            {
                if (poField->GetSubType() == OFSTBoolean)
                    paeFieldTypes[iField] = SWQ_BOOLEAN;
                else
                    paeFieldTypes[iField] = SWQ_INTEGER64;
                break;
            }

            case OFTReal:
                paeFieldTypes[iField] = SWQ_FLOAT;
                break;

            case OFTString:
                paeFieldTypes[iField] = SWQ_STRING;
                break;

            case OFTDate:
            case OFTTime:
            case OFTDateTime:
                paeFieldTypes[iField] = SWQ_TIMESTAMP;
                break;

            default:
                paeFieldTypes[iField] = SWQ_OTHER;
                break;
        }
    }

    int iField = 0;
    while (iField < SPECIAL_FIELD_COUNT)
    {
        papszFieldNames[poDefn->GetFieldCount() + iField] =
            const_cast<char *>(SpecialFieldNames[iField]);
        paeFieldTypes[poDefn->GetFieldCount() + iField] =
            (iField == SPF_FID) ? SWQ_INTEGER64 : SpecialFieldTypes[iField];
        ++iField;
    }

    for (iField = 0; iField < poDefn->GetGeomFieldCount(); iField++)
    {
        OGRGeomFieldDefn *poField = poDefn->GetGeomFieldDefn(iField);
        const int iDstField =
            poDefn->GetFieldCount() + SPECIAL_FIELD_COUNT + iField;

        papszFieldNames[iDstField] = const_cast<char *>(poField->GetNameRef());
        if (*papszFieldNames[iDstField] == '\0')
            papszFieldNames[iDstField] =
                const_cast<char *>(OGR_GEOMETRY_DEFAULT_NON_EMPTY_NAME);
        paeFieldTypes[iDstField] = SWQ_GEOMETRY;
    }

    if (bMustAddFID)
    {
        papszFieldNames[nFieldCount - 1] = const_cast<char *>(pszFIDColumn);
        paeFieldTypes[nFieldCount - 1] =
            (poLayer != nullptr &&
             poLayer->GetMetadataItem(OLMD_FID64) != nullptr &&
             EQUAL(poLayer->GetMetadataItem(OLMD_FID64), "YES"))
                ? SWQ_INTEGER64
                : SWQ_INTEGER;
    }

    // Try to parse.
    poTargetDefn = poDefn;
    const CPLErr eCPLErr = swq_expr_compile(
        pszExpression, nFieldCount, papszFieldNames, paeFieldTypes, bCheck,
        poCustomFuncRegistrar, reinterpret_cast<swq_expr_node **>(&pSWQExpr));

    OGRErr eErr = OGRERR_NONE;
    if (eCPLErr != CE_None)
    {
        eErr = OGRERR_CORRUPT_DATA;
        pSWQExpr = nullptr;
    }

    CPLFree(papszFieldNames);
    CPLFree(paeFieldTypes);

    return eErr;
}

/************************************************************************/
/*                    OGRFeatureFetcherFixFieldIndex()                  */
/************************************************************************/

static int OGRFeatureFetcherFixFieldIndex(OGRFeatureDefn *poFDefn, int nIdx)
{
    /* Nastry trick: if we inserted the FID column as an extra column, it is */
    /* after regular fields, special fields and geometry fields */
    if (nIdx == poFDefn->GetFieldCount() + SPECIAL_FIELD_COUNT +
                    poFDefn->GetGeomFieldCount())
    {
        return poFDefn->GetFieldCount() + SPF_FID;
    }
    return nIdx;
}

/************************************************************************/
/*                         OGRFeatureFetcher()                          */
/************************************************************************/

static swq_expr_node *OGRFeatureFetcher(swq_expr_node *op, void *pFeatureIn)

{
    OGRFeature *poFeature = static_cast<OGRFeature *>(pFeatureIn);

    if (op->field_type == SWQ_GEOMETRY)
    {
        const int iField = op->field_index -
                           (poFeature->GetFieldCount() + SPECIAL_FIELD_COUNT);
        swq_expr_node *poRetNode =
            new swq_expr_node(poFeature->GetGeomFieldRef(iField));
        return poRetNode;
    }

    const int idx = OGRFeatureFetcherFixFieldIndex(poFeature->GetDefnRef(),
                                                   op->field_index);

    swq_expr_node *poRetNode = nullptr;
    switch (op->field_type)
    {
        case SWQ_INTEGER:
        case SWQ_BOOLEAN:
            poRetNode = new swq_expr_node(poFeature->GetFieldAsInteger(idx));
            break;

        case SWQ_INTEGER64:
            poRetNode = new swq_expr_node(poFeature->GetFieldAsInteger64(idx));
            break;

        case SWQ_FLOAT:
            poRetNode = new swq_expr_node(poFeature->GetFieldAsDouble(idx));
            break;

        case SWQ_TIMESTAMP:
            poRetNode = new swq_expr_node(poFeature->GetFieldAsString(idx));
            poRetNode->MarkAsTimestamp();
            break;

        default:
            poRetNode = new swq_expr_node(poFeature->GetFieldAsString(idx));
            break;
    }

    poRetNode->is_null = !(poFeature->IsFieldSetAndNotNull(idx));

    return poRetNode;
}

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

int OGRFeatureQuery::Evaluate(OGRFeature *poFeature)

{
    if (pSWQExpr == nullptr)
        return FALSE;

    swq_expr_node *poResult = static_cast<swq_expr_node *>(pSWQExpr)->Evaluate(
        OGRFeatureFetcher, poFeature, *m_psContext);

    if (poResult == nullptr)
        return FALSE;

    bool bLogicalResult = false;
    if (poResult->field_type == SWQ_INTEGER ||
        poResult->field_type == SWQ_INTEGER64 ||
        poResult->field_type == SWQ_BOOLEAN)
        bLogicalResult = CPL_TO_BOOL(static_cast<int>(poResult->int_value));

    delete poResult;

    return bLogicalResult;
}

/************************************************************************/
/*                            CanUseIndex()                             */
/************************************************************************/

int OGRFeatureQuery::CanUseIndex(OGRLayer *poLayer)
{
    swq_expr_node *psExpr = static_cast<swq_expr_node *>(pSWQExpr);

    // Do we have an index on the targeted layer?
    if (poLayer->GetIndex() == nullptr)
        return FALSE;

    return CanUseIndex(psExpr, poLayer);
}

int OGRFeatureQuery::CanUseIndex(const swq_expr_node *psExpr, OGRLayer *poLayer)
{
    // Does the expression meet our requirements?
    if (psExpr == nullptr || psExpr->eNodeType != SNT_OPERATION)
        return FALSE;

    if ((psExpr->nOperation == SWQ_OR || psExpr->nOperation == SWQ_AND) &&
        psExpr->nSubExprCount == 2)
    {
        return CanUseIndex(psExpr->papoSubExpr[0], poLayer) &&
               CanUseIndex(psExpr->papoSubExpr[1], poLayer);
    }

    if (!(psExpr->nOperation == SWQ_EQ || psExpr->nOperation == SWQ_IN) ||
        psExpr->nSubExprCount < 2)
        return FALSE;

    swq_expr_node *poColumn = psExpr->papoSubExpr[0];
    swq_expr_node *poValue = psExpr->papoSubExpr[1];

    if (poColumn->eNodeType != SNT_COLUMN || poValue->eNodeType != SNT_CONSTANT)
        return FALSE;

    OGRAttrIndex *poIndex =
        poLayer->GetIndex()->GetFieldIndex(OGRFeatureFetcherFixFieldIndex(
            poLayer->GetLayerDefn(), poColumn->field_index));
    if (poIndex == nullptr)
        return FALSE;

    // Have an index.
    return TRUE;
}

/************************************************************************/
/*                       EvaluateAgainstIndices()                       */
/*                                                                      */
/*      Attempt to return a list of FIDs matching the given             */
/*      attribute query conditions utilizing attribute indices.         */
/*      Returns NULL if the result cannot be computed from the          */
/*      available indices, or an "OGRNullFID" terminated list of        */
/*      FIDs if it can.                                                 */
/*                                                                      */
/*      For now we only support equality tests on a single indexed      */
/*      attribute field.  Eventually we should make this support        */
/*      multi-part queries with ranges.                                 */
/************************************************************************/

GIntBig *OGRFeatureQuery::EvaluateAgainstIndices(OGRLayer *poLayer,
                                                 OGRErr *peErr)

{
    swq_expr_node *psExpr = static_cast<swq_expr_node *>(pSWQExpr);

    if (peErr != nullptr)
        *peErr = OGRERR_NONE;

    // Do we have an index on the targeted layer?
    if (poLayer->GetIndex() == nullptr)
        return nullptr;

    GIntBig nFIDCount = 0;
    return EvaluateAgainstIndices(psExpr, poLayer, nFIDCount);
}

// The input arrays must be sorted.
static GIntBig *OGRORGIntBigArray(GIntBig panFIDList1[], GIntBig nFIDCount1,
                                  GIntBig panFIDList2[], GIntBig nFIDCount2,
                                  GIntBig &nFIDCount)
{
    const GIntBig nMaxCount = nFIDCount1 + nFIDCount2;
    GIntBig *panFIDList = static_cast<GIntBig *>(
        CPLMalloc(static_cast<size_t>(nMaxCount + 1) * sizeof(GIntBig)));
    nFIDCount = 0;

    for (GIntBig i1 = 0, i2 = 0; i1 < nFIDCount1 || i2 < nFIDCount2;)
    {
        if (i1 < nFIDCount1 && i2 < nFIDCount2)
        {
            const GIntBig nVal1 = panFIDList1[i1];
            const GIntBig nVal2 = panFIDList2[i2];
            if (nVal1 < nVal2)
            {
                if (i1 + 1 < nFIDCount1 && panFIDList1[i1 + 1] <= nVal2)
                {
                    panFIDList[nFIDCount++] = nVal1;
                    i1++;
                }
                else
                {
                    panFIDList[nFIDCount++] = nVal1;
                    panFIDList[nFIDCount++] = nVal2;
                    i1++;
                    i2++;
                }
            }
            else if (nVal1 == nVal2)
            {
                panFIDList[nFIDCount++] = nVal1;
                i1++;
                i2++;
            }
            else
            {
                if (i2 + 1 < nFIDCount2 && panFIDList2[i2 + 1] <= nVal1)
                {
                    panFIDList[nFIDCount++] = nVal2;
                    i2++;
                }
                else
                {
                    panFIDList[nFIDCount++] = nVal2;
                    panFIDList[nFIDCount++] = nVal1;
                    i1++;
                    i2++;
                }
            }
        }
        else if (i1 < nFIDCount1)
        {
            const GIntBig nVal1 = panFIDList1[i1];
            panFIDList[nFIDCount++] = nVal1;
            i1++;
        }
        else if (i2 < nFIDCount2)
        {
            const GIntBig nVal2 = panFIDList2[i2];
            panFIDList[nFIDCount++] = nVal2;
            i2++;
        }
    }

    panFIDList[nFIDCount] = OGRNullFID;

    return panFIDList;
}

// The input arrays must be sorted.
static GIntBig *OGRANDGIntBigArray(GIntBig panFIDList1[], GIntBig nFIDCount1,
                                   GIntBig panFIDList2[], GIntBig nFIDCount2,
                                   GIntBig &nFIDCount)
{
    GIntBig nMaxCount = std::max(nFIDCount1, nFIDCount2);
    GIntBig *panFIDList = static_cast<GIntBig *>(
        CPLMalloc(static_cast<size_t>(nMaxCount + 1) * sizeof(GIntBig)));
    nFIDCount = 0;

    for (GIntBig i1 = 0, i2 = 0; i1 < nFIDCount1 && i2 < nFIDCount2;)
    {
        const GIntBig nVal1 = panFIDList1[i1];
        const GIntBig nVal2 = panFIDList2[i2];
        if (nVal1 < nVal2)
        {
            if (i1 + 1 < nFIDCount1 && panFIDList1[i1 + 1] <= nVal2)
            {
                i1++;
            }
            else
            {
                i1++;
                i2++;
            }
        }
        else if (nVal1 == nVal2)
        {
            panFIDList[nFIDCount++] = nVal1;
            i1++;
            i2++;
        }
        else
        {
            if (i2 + 1 < nFIDCount2 && panFIDList2[i2 + 1] <= nVal1)
            {
                i2++;
            }
            else
            {
                i1++;
                i2++;
            }
        }
    }

    panFIDList[nFIDCount] = OGRNullFID;

    return panFIDList;
}

GIntBig *OGRFeatureQuery::EvaluateAgainstIndices(const swq_expr_node *psExpr,
                                                 OGRLayer *poLayer,
                                                 GIntBig &nFIDCount)
{
    // Does the expression meet our requirements?
    if (psExpr == nullptr || psExpr->eNodeType != SNT_OPERATION)
        return nullptr;

    if ((psExpr->nOperation == SWQ_OR || psExpr->nOperation == SWQ_AND) &&
        psExpr->nSubExprCount == 2)
    {
        GIntBig nFIDCount1 = 0;
        GIntBig nFIDCount2 = 0;
        GIntBig *panFIDList1 =
            EvaluateAgainstIndices(psExpr->papoSubExpr[0], poLayer, nFIDCount1);
        GIntBig *panFIDList2 =
            panFIDList1 == nullptr
                ? nullptr
                : EvaluateAgainstIndices(psExpr->papoSubExpr[1], poLayer,
                                         nFIDCount2);
        GIntBig *panFIDList = nullptr;
        if (panFIDList1 != nullptr && panFIDList2 != nullptr)
        {
            if (psExpr->nOperation == SWQ_OR)
                panFIDList =
                    OGRORGIntBigArray(panFIDList1, nFIDCount1, panFIDList2,
                                      nFIDCount2, nFIDCount);
            else if (psExpr->nOperation == SWQ_AND)
                panFIDList =
                    OGRANDGIntBigArray(panFIDList1, nFIDCount1, panFIDList2,
                                       nFIDCount2, nFIDCount);
        }
        CPLFree(panFIDList1);
        CPLFree(panFIDList2);
        return panFIDList;
    }

    if (!(psExpr->nOperation == SWQ_EQ || psExpr->nOperation == SWQ_IN) ||
        psExpr->nSubExprCount < 2)
        return nullptr;

    const swq_expr_node *poColumn = psExpr->papoSubExpr[0];
    const swq_expr_node *poValue = psExpr->papoSubExpr[1];

    if (poColumn->eNodeType != SNT_COLUMN || poValue->eNodeType != SNT_CONSTANT)
        return nullptr;

    const int nIdx = OGRFeatureFetcherFixFieldIndex(poLayer->GetLayerDefn(),
                                                    poColumn->field_index);

    OGRAttrIndex *poIndex = poLayer->GetIndex()->GetFieldIndex(nIdx);
    if (poIndex == nullptr)
        return nullptr;

    // Have an index, now we need to query it.
    OGRField sValue;
    const OGRFieldDefn *poFieldDefn =
        poLayer->GetLayerDefn()->GetFieldDefn(nIdx);

    // Handle the case of an IN operation.
    if (psExpr->nOperation == SWQ_IN)
    {
        int nLength = 0;
        GIntBig *panFIDs = nullptr;
        nFIDCount = 0;

        for (int iIN = 1; iIN < psExpr->nSubExprCount; iIN++)
        {
            switch (poFieldDefn->GetType())
            {
                case OFTInteger:
                    if (psExpr->papoSubExpr[iIN]->field_type == SWQ_FLOAT)
                        sValue.Integer = static_cast<int>(
                            psExpr->papoSubExpr[iIN]->float_value);
                    else
                        sValue.Integer = static_cast<int>(
                            psExpr->papoSubExpr[iIN]->int_value);
                    break;

                case OFTInteger64:
                    if (psExpr->papoSubExpr[iIN]->field_type == SWQ_FLOAT)
                        sValue.Integer64 = static_cast<GIntBig>(
                            psExpr->papoSubExpr[iIN]->float_value);
                    else
                        sValue.Integer64 = psExpr->papoSubExpr[iIN]->int_value;
                    break;

                case OFTReal:
                    sValue.Real = psExpr->papoSubExpr[iIN]->float_value;
                    break;

                case OFTString:
                    sValue.String = psExpr->papoSubExpr[iIN]->string_value;
                    break;

                default:
                    CPLAssert(false);
                    return nullptr;
            }

            int nFIDCount32 = static_cast<int>(nFIDCount);
            panFIDs = poIndex->GetAllMatches(&sValue, panFIDs, &nFIDCount32,
                                             &nLength);
            nFIDCount = nFIDCount32;
        }

        if (nFIDCount > 1)
        {
            // The returned FIDs are expected to be in sorted order.
            std::sort(panFIDs, panFIDs + nFIDCount);
        }
        return panFIDs;
    }

    // Handle equality test.
    switch (poFieldDefn->GetType())
    {
        case OFTInteger:
            if (poValue->field_type == SWQ_FLOAT)
                sValue.Integer = static_cast<int>(poValue->float_value);
            else
                sValue.Integer = static_cast<int>(poValue->int_value);
            break;

        case OFTInteger64:
            if (poValue->field_type == SWQ_FLOAT)
                sValue.Integer64 = static_cast<GIntBig>(poValue->float_value);
            else
                sValue.Integer64 = poValue->int_value;
            break;

        case OFTReal:
            sValue.Real = poValue->float_value;
            break;

        case OFTString:
            sValue.String = poValue->string_value;
            break;

        default:
            CPLAssert(false);
            return nullptr;
    }

    int nLength = 0;
    int nFIDCount32 = 0;
    GIntBig *panFIDs =
        poIndex->GetAllMatches(&sValue, nullptr, &nFIDCount32, &nLength);
    nFIDCount = nFIDCount32;
    if (nFIDCount > 1)
    {
        // The returned FIDs are expected to be sorted.
        std::sort(panFIDs, panFIDs + nFIDCount);
    }
    return panFIDs;
}

/************************************************************************/
/*                         OGRFieldCollector()                          */
/*                                                                      */
/*      Helper function for recursing through tree to satisfy           */
/*      GetUsedFields().                                                */
/************************************************************************/

char **OGRFeatureQuery::FieldCollector(void *pBareOp, char **papszList)

{
    swq_expr_node *op = static_cast<swq_expr_node *>(pBareOp);

    // References to tables other than the primarily are currently unsupported.
    // Error out.
    if (op->eNodeType == SNT_COLUMN)
    {
        if (op->table_index != 0)
        {
            CSLDestroy(papszList);
            return nullptr;
        }

        // Add the field name into our list if it is not already there.
        const char *pszFieldName = nullptr;
        const int nIdx =
            OGRFeatureFetcherFixFieldIndex(poTargetDefn, op->field_index);

        if (nIdx >= poTargetDefn->GetFieldCount() &&
            nIdx < poTargetDefn->GetFieldCount() + SPECIAL_FIELD_COUNT)
        {
            pszFieldName =
                SpecialFieldNames[nIdx - poTargetDefn->GetFieldCount()];
        }
        else if (nIdx >= 0 && nIdx < poTargetDefn->GetFieldCount())
        {
            auto poFieldDefn = poTargetDefn->GetFieldDefn(nIdx);
            if (!poFieldDefn)
            {
                CPLAssert(false);
                CSLDestroy(papszList);
                return nullptr;
            }
            pszFieldName = poFieldDefn->GetNameRef();
        }
        else
        {
            CSLDestroy(papszList);
            return nullptr;
        }

        if (CSLFindString(papszList, pszFieldName) == -1)
            papszList = CSLAddString(papszList, pszFieldName);
    }

    // Add in fields from subexpressions.
    if (op->eNodeType == SNT_OPERATION)
    {
        for (int iSubExpr = 0; iSubExpr < op->nSubExprCount; iSubExpr++)
        {
            papszList = FieldCollector(op->papoSubExpr[iSubExpr], papszList);
        }
    }

    return papszList;
}

/************************************************************************/
/*                           GetUsedFields()                            */
/************************************************************************/

/**
 * Returns lists of fields in expression.
 *
 * All attribute fields are used in the expression of this feature
 * query are returned as a StringList of field names.  This function would
 * primarily be used within drivers to recognise special case conditions
 * depending only on attribute fields that can be very efficiently
 * fetched.
 *
 * NOTE: If any fields in the expression are from tables other than the
 * primary table then NULL is returned indicating an error.  In successful
 * use, no non-empty expression should return an empty list.
 *
 * @return list of field names.  Free list with CSLDestroy() when no longer
 * required.
 */

char **OGRFeatureQuery::GetUsedFields()

{
    if (pSWQExpr == nullptr)
        return nullptr;

    return FieldCollector(pSWQExpr, nullptr);
}

//! @endcond
