/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Implements OGRSQLiteLayer class, code shared between
 *           the direct table access, and the generic SQL results.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 *
 * Contributor: Alessandro Furieri, a.furieri@lqt.it
 * Portions of this module supporting SpatiaLite's own 3D geometries
 * [XY, XYM, XYZ and XYZM] available since v.2.4.0
 * Developed for Faunalia ( http://www.faunalia.it) with funding from
 * Regione Toscana - Settore SISTEMA INFORMATIVO TERRITORIALE ED AMBIENTALE
 *
 ******************************************************************************
 * Copyright (c) 2004, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2009-2021, Even Rouault <even dot rouault at spatialys.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "cpl_port.h"
#include "ogr_sqlite.h"
#include "ogrsqliteutility.h"

#include <algorithm>
#include <cassert>
#include <climits>
#include <cmath>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <map>
#include <set>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_string.h"
#include "ogr_core.h"
#include "ogr_feature.h"
#include "ogr_geometry.h"
#include "ogr_spatialref.h"
#include "ogrsf_frmts.h"
#include "sqlite3.h"

/************************************************************************/
/*                           OGRSQLiteLayer()                           */
/************************************************************************/

OGRSQLiteLayer::OGRSQLiteLayer(OGRSQLiteDataSource *poDSIn)
    : m_poDS(poDSIn),
      m_bUseComprGeom(CPLTestBool(CPLGetConfigOption("COMPRESS_GEOM", "FALSE")))
{
}

/************************************************************************/
/*                          ~OGRSQLiteLayer()                           */
/************************************************************************/

OGRSQLiteLayer::~OGRSQLiteLayer()

{
    Finalize();
}

/************************************************************************/
/*                               Finalize()                             */
/************************************************************************/

void OGRSQLiteLayer::Finalize()
{
    /* Caution: this function can be called several times (see */
    /* OGRSQLiteExecuteSQLLayer::~OGRSQLiteExecuteSQLLayer()), so it must */
    /* be a no-op on second call */

    if (m_nFeaturesRead > 0 && m_poFeatureDefn != nullptr)
    {
        CPLDebug("SQLite", CPL_FRMT_GIB " features read on layer '%s'.",
                 m_nFeaturesRead, m_poFeatureDefn->GetName());
    }

    if (m_hStmt != nullptr)
    {
        sqlite3_finalize(m_hStmt);
        m_hStmt = nullptr;
    }

    if (m_poFeatureDefn != nullptr)
    {
        m_poFeatureDefn->Release();
        m_poFeatureDefn = nullptr;
    }

    CPLFree(m_pszFIDColumn);
    m_pszFIDColumn = nullptr;
    CPLFree(m_panFieldOrdinals);
    m_panFieldOrdinals = nullptr;

    CSLDestroy(m_papszCompressedColumns);
    m_papszCompressedColumns = nullptr;
}

/************************************************************************/
/*                      OGRGetDateTimeFieldType()                       */
/************************************************************************/

static bool OGRGetDateTimeFieldType(const char *pszValue,
                                    OGRFieldType *pFieldType)
{
    bool bSuccess = false;
    size_t nValueLength = CPLStrnlen(pszValue, 16);

    if (nValueLength >= 5)
    {
        unsigned int nYear;
        unsigned int nMonth;
        unsigned int nDay;
        unsigned int nHour;
        unsigned int nMinute;

        if (nValueLength >= 10)
        {
            int nItemsMatched =
                sscanf(pszValue, "%04u-%02u-%02u", &nYear, &nMonth, &nDay);
            if (nItemsMatched == 1)
                nItemsMatched =
                    sscanf(pszValue, "%04u/%02u/%02u", &nYear, &nMonth, &nDay);

            if ((nItemsMatched == 3) && (nValueLength >= 16))
                nItemsMatched +=
                    sscanf(&pszValue[11], "%02u:%02u", &nHour, &nMinute);

            if (nItemsMatched >= 3)
            {
                *pFieldType = (nItemsMatched == 5) ? OFTDateTime : OFTDate;
                bSuccess = true;
            }
        }
        else if (sscanf(pszValue, "%02u:%02u", &nHour, &nMinute) == 2)
        {
            *pFieldType = OFTTime;
            bSuccess = true;
        }
    }

    return bSuccess;
}

/************************************************************************/
/*                          OGRIsBinaryGeomCol()                        */
/************************************************************************/

static int OGRIsBinaryGeomCol(sqlite3_stmt *hStmt, int iCol,
                              CPL_UNUSED OGRFieldDefn &oField,
                              OGRSQLiteGeomFormat &eGeomFormat)
{
    OGRGeometry *poGeometry = nullptr;
    const int nBytes = sqlite3_column_bytes(hStmt, iCol);
    // coverity[tainted_data_return]
    const GByte *pabyBlob =
        reinterpret_cast<const GByte *>(sqlite3_column_blob(hStmt, iCol));
    int nBytesConsumed = 0;
    CPLPushErrorHandler(CPLQuietErrorHandler);
    /* Try as spatialite first since createFromWkb() can sometimes */
    /* interpret spatialite blobs as WKB for certain SRID values */
    if (OGRSQLiteLayer::ImportSpatiaLiteGeometry(pabyBlob, nBytes,
                                                 &poGeometry) == OGRERR_NONE)
    {
        eGeomFormat = OSGF_SpatiaLite;
    }
    else if (OGRGeometryFactory::createFromWkb(pabyBlob, nullptr, &poGeometry,
                                               nBytes) == OGRERR_NONE)
    {
        eGeomFormat = OSGF_WKB;
    }
    else if (OGRGeometryFactory::createFromFgf(pabyBlob, nullptr, &poGeometry,
                                               nBytes, &nBytesConsumed) ==
                 OGRERR_NONE &&
             nBytes == nBytesConsumed)
    {
        eGeomFormat = OSGF_FGF;
    }
    CPLPopErrorHandler();
    CPLErrorReset();
    delete poGeometry;
    if (eGeomFormat != OSGF_None)
    {
        return TRUE;
    }
    return FALSE;
}

/************************************************************************/
/*                          BuildFeatureDefn()                          */
/*                                                                      */
/*      Build feature definition from a set of column definitions       */
/*      set on a statement.  Sift out geometry and FID fields.          */
/************************************************************************/

void OGRSQLiteLayer::BuildFeatureDefn(const char *pszLayerName, bool bIsSelect,
                                      sqlite3_stmt *hStmtIn,
                                      const std::set<CPLString> *paosGeomCols,
                                      const std::set<CPLString> &aosIgnoredCols)

{
    m_poFeatureDefn = new OGRSQLiteFeatureDefn(pszLayerName);
    m_poFeatureDefn->SetGeomType(wkbNone);
    m_poFeatureDefn->Reference();

    std::map<std::string, std::string> oMapTableInfo;  // name to type
    if (!bIsSelect)
    {
        // oField.GetNameRef() can be better than sqlite3_column_name() on views
        char *pszSQL = sqlite3_mprintf("PRAGMA table_info('%q')", pszLayerName);
        auto oResultTable = SQLQuery(m_poDS->GetDB(), pszSQL);
        sqlite3_free(pszSQL);
        if (oResultTable && oResultTable->ColCount() == 6)
        {
            for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
            {
                const char *pszName = oResultTable->GetValue(1, iRecord);
                const char *pszType = oResultTable->GetValue(2, iRecord);
                if (pszName && pszType)
                    oMapTableInfo[pszName] = pszType;
            }
        }
    }

    const int nRawColumns = sqlite3_column_count(hStmtIn);

    m_panFieldOrdinals =
        static_cast<int *>(CPLMalloc(sizeof(int) * nRawColumns));

    std::set<std::string> oSetFields;

    for (int iCol = 0; iCol < nRawColumns; iCol++)
    {
        OGRFieldDefn oField(SQLUnescape(sqlite3_column_name(hStmtIn, iCol)),
                            OFTString);
        const char *pszFieldName = oField.GetNameRef();

        // In some cases, particularly when there is a real name for
        // the primary key/_rowid_ column we will end up getting the
        // primary key column appearing twice.  Ignore any repeated names.
        if (cpl::contains(oSetFields, pszFieldName))
            continue;

        if (EQUAL(pszFieldName, "OGR_NATIVE_DATA"))
        {
            m_iOGRNativeDataCol = iCol;
            continue;
        }

        if (EQUAL(pszFieldName, "OGR_NATIVE_MEDIA_TYPE"))
        {
            m_iOGRNativeMediaTypeCol = iCol;
            continue;
        }

        /* In the case of Spatialite VirtualShape, the PKUID */
        /* should be considered as a primary key */
        if (m_bIsVirtualShape && EQUAL(pszFieldName, "PKUID"))
        {
            CPLFree(m_pszFIDColumn);
            m_pszFIDColumn = CPLStrdup(pszFieldName);
        }

        if (m_pszFIDColumn != nullptr && EQUAL(m_pszFIDColumn, pszFieldName))
            continue;

        // oField.SetWidth( std::max(0,poStmt->GetColSize( iCol )) );

        if (aosIgnoredCols.find(CPLString(pszFieldName).tolower()) !=
            aosIgnoredCols.end())
        {
            continue;
        }
        if (paosGeomCols != nullptr &&
            paosGeomCols->find(CPLString(pszFieldName).tolower()) !=
                paosGeomCols->end())
        {
            m_poFeatureDefn->AddGeomFieldDefn(
                std::make_unique<OGRSQLiteGeomFieldDefn>(pszFieldName, iCol));
            continue;
        }

        const int nColType = sqlite3_column_type(hStmtIn, iCol);
        switch (nColType)
        {
            case SQLITE_INTEGER:
                if (CPLTestBool(CPLGetConfigOption("OGR_PROMOTE_TO_INTEGER64",
                                                   "FALSE")))
                    oField.SetType(OFTInteger64);
                else
                {
                    GIntBig nVal = sqlite3_column_int64(hStmtIn, iCol);
                    if (CPL_INT64_FITS_ON_INT32(nVal))
                        oField.SetType(OFTInteger);
                    else
                        oField.SetType(OFTInteger64);
                }
                break;

            case SQLITE_FLOAT:
                oField.SetType(OFTReal);
                break;

            case SQLITE_BLOB:
                oField.SetType(OFTBinary);
                break;

            default:
                /* leave it as OFTString */;
        }

        const char *pszDeclType = sqlite3_column_decltype(hStmtIn, iCol);
        if (pszDeclType == nullptr)
        {
            auto iter = oMapTableInfo.find(pszFieldName);
            if (iter != oMapTableInfo.end())
                pszDeclType = iter->second.c_str();
        }
        // CPLDebug("SQLITE", "decltype(%s) = %s",
        //          pszFieldName, pszDeclType ? pszDeclType : "null");
        OGRFieldType eFieldType = OFTString;
        if (pszDeclType != nullptr)
        {
            std::string osDeclType(pszDeclType);
            const char *pszBeginDomainName =
                strstr(pszDeclType, "_BEGIN_DOMAIN_NAME_");
            if (pszBeginDomainName)
            {
                const char *pszBeginDomainNameOri = pszBeginDomainName;
                pszBeginDomainName += strlen("_BEGIN_DOMAIN_NAME_");
                const char *pszEndDomainName =
                    strstr(pszBeginDomainName, "_END_DOMAIN_NAME");
                if (pszEndDomainName)
                {
                    std::string osHEXDomainName(pszBeginDomainName,
                                                pszEndDomainName -
                                                    pszBeginDomainName);
                    int nBytes = 0;
                    GByte *pabyDecoded =
                        CPLHexToBinary(osHEXDomainName.c_str(), &nBytes);
                    oField.SetDomainName(std::string(
                        reinterpret_cast<const char *>(pabyDecoded), nBytes));
                    CPLFree(pabyDecoded);
                    osDeclType =
                        std::string(pszDeclType,
                                    pszBeginDomainNameOri - pszDeclType) +
                        std::string(pszEndDomainName +
                                    strlen(pszEndDomainName));
                }
            }
            pszDeclType = osDeclType.c_str();

            if (EQUAL(pszDeclType, "INTEGER_BOOLEAN") ||
                EQUAL(pszDeclType, "BOOLEAN"))
            {
                oField.SetType(OFTInteger);
                oField.SetSubType(OFSTBoolean);
            }
            else if (EQUAL(pszDeclType, "INTEGER_INT16"))
            {
                oField.SetType(OFTInteger);
                oField.SetSubType(OFSTInt16);
            }
            else if (EQUAL(pszDeclType, "INTEGER_OR_TEXT"))
            {
                // Used by PROJ proj.db
                oField.SetType(OFTString);
            }
            else if (EQUAL(pszDeclType, "JSONINTEGERLIST"))
            {
                oField.SetType(OFTIntegerList);
            }
            else if (EQUAL(pszDeclType, "JSONINTEGER64LIST"))
            {
                oField.SetType(OFTInteger64List);
            }
            else if (EQUAL(pszDeclType, "JSONREALLIST"))
            {
                oField.SetType(OFTRealList);
            }
            else if (EQUAL(pszDeclType, "JSONSTRINGLIST"))
            {
                oField.SetType(OFTStringList);
            }
            else if (EQUAL(pszDeclType, "BIGINT") ||
                     EQUAL(pszDeclType, "INT8") ||
                     EQUAL(pszDeclType, "UNSIGNED BIG INT"))
            {
                oField.SetType(OFTInteger64);
            }
            else if (STARTS_WITH_CI(pszDeclType, "INTEGER") ||
                     EQUAL(pszDeclType, "INT") ||
                     EQUAL(pszDeclType, "TINYINT") ||
                     EQUAL(pszDeclType, "SMALLINT") ||
                     EQUAL(pszDeclType, "MEDIUMINT") ||
                     EQUAL(pszDeclType, "INT2"))
            {
                oField.SetType(OFTInteger);
            }
            else if (EQUAL(pszDeclType, "FLOAT_FLOAT32"))
            {
                oField.SetType(OFTReal);
                oField.SetSubType(OFSTFloat32);
            }
            else if (EQUAL(pszDeclType, "FLOAT") ||
                     EQUAL(pszDeclType, "DECIMAL") ||
                     EQUAL(pszDeclType, "REAL") ||
                     EQUAL(pszDeclType, "DOUBLE") ||
                     EQUAL(pszDeclType, "DOUBLE PRECISION") ||
                     EQUAL(pszDeclType, "NUMERIC"))
            {
                oField.SetType(OFTReal);
            }
            else if (STARTS_WITH_CI(pszDeclType, "BLOB"))
            {
                oField.SetType(OFTBinary);
                /* Parse format like BLOB_POINT_25D_4326 created by */
                /* OGRSQLiteExecuteSQL() */
                if (pszDeclType[4] == '_')
                {
                    char *pszDeclTypeDup = CPLStrdup(pszDeclType);
                    char *pszNextUnderscore = strchr(pszDeclTypeDup + 5, '_');
                    const char *pszGeomType = pszDeclTypeDup + 5;
                    if (pszNextUnderscore != nullptr)
                    {
                        *pszNextUnderscore = '\0';
                        pszNextUnderscore++;
                        int nSRID = -1;
                        const char *pszCoordDimension = pszNextUnderscore;
                        pszNextUnderscore = strchr(pszNextUnderscore, '_');
                        if (pszNextUnderscore != nullptr)
                        {
                            *pszNextUnderscore = '\0';
                            pszNextUnderscore++;
                            const char *pszSRID = pszNextUnderscore;
                            nSRID = atoi(pszSRID);
                        }

                        OGRwkbGeometryType eGeomType =
                            OGRFromOGCGeomType(pszGeomType);
                        if (EQUAL(pszCoordDimension, "XYZ"))
                            eGeomType = wkbSetZ(eGeomType);
                        else if (EQUAL(pszCoordDimension, "XYM"))
                            eGeomType = wkbSetM(eGeomType);
                        else if (EQUAL(pszCoordDimension, "XYZM"))
                            eGeomType = wkbSetM(wkbSetZ(eGeomType));
                        OGRSpatialReference *poSRS = m_poDS->FetchSRS(nSRID);
                        auto poGeomFieldDefn =
                            std::make_unique<OGRSQLiteGeomFieldDefn>(
                                pszFieldName, iCol);
                        poGeomFieldDefn->m_eGeomFormat = OSGF_SpatiaLite;
                        poGeomFieldDefn->SetSpatialRef(poSRS);
                        poGeomFieldDefn->SetType(eGeomType);
                        m_poFeatureDefn->AddGeomFieldDefn(
                            std::move(poGeomFieldDefn));
                        CPLFree(pszDeclTypeDup);
                        continue;
                    }
                    CPLFree(pszDeclTypeDup);
                }
            }
            else if (EQUAL(pszDeclType, "TEXT") ||
                     STARTS_WITH_CI(pszDeclType, "VARCHAR") ||
                     STARTS_WITH_CI(pszDeclType, "CHARACTER") ||
                     STARTS_WITH_CI(pszDeclType, "VARYING CHARACTER") ||
                     STARTS_WITH_CI(pszDeclType, "NCHAR") ||
                     STARTS_WITH_CI(pszDeclType, "NATIVE CHARACTER") ||
                     STARTS_WITH_CI(pszDeclType, "NVARCHAR") ||
                     EQUAL(pszDeclType, "CLOB"))
            {
                oField.SetType(OFTString);
                if (strstr(pszDeclType, "_deflate") != nullptr)
                {
                    if (CSLFindString(m_papszCompressedColumns, pszFieldName) <
                        0)
                    {
                        m_papszCompressedColumns = CSLAddString(
                            m_papszCompressedColumns, pszFieldName);
                        CPLDebug("SQLITE", "%s is compressed", pszFieldName);
                    }
                }
            }
            else if ((EQUAL(pszDeclType, "TIMESTAMP") ||
                      EQUAL(pszDeclType, "DATETIME")) &&
                     (nColType == SQLITE_TEXT || nColType == SQLITE_FLOAT ||
                      nColType == SQLITE_NULL))
                eFieldType = OFTDateTime;
            else if (EQUAL(pszDeclType, "DATE") &&
                     (nColType == SQLITE_TEXT || nColType == SQLITE_FLOAT ||
                      nColType == SQLITE_NULL))
                eFieldType = OFTDate;
            else if (EQUAL(pszDeclType, "TIME") &&
                     (nColType == SQLITE_TEXT || nColType == SQLITE_FLOAT ||
                      nColType == SQLITE_NULL))
                eFieldType = OFTTime;
        }
        else if (nColType == SQLITE_TEXT &&
                 (STARTS_WITH_CI(pszFieldName, "MIN(") ||
                  STARTS_WITH_CI(pszFieldName, "MAX(")))
        {
            const char *pszText = reinterpret_cast<const char *>(
                sqlite3_column_text(hStmtIn, iCol));
            if (pszText != nullptr)
            {
                OGRField oScratchField;

                if (OGRParseDate(pszText, &oScratchField, 0))
                    OGRGetDateTimeFieldType(pszText, &eFieldType);
            }
        }

        // Recognise some common geometry column names.
        if (paosGeomCols == nullptr &&
            (EQUAL(pszFieldName, "wkt_geometry") ||
             EQUAL(pszFieldName, "geometry") ||
             STARTS_WITH_CI(pszFieldName, "asbinary(") ||
             STARTS_WITH_CI(pszFieldName, "astext(") ||
             (STARTS_WITH_CI(pszFieldName, "st_") &&
              nColType == SQLITE_BLOB)) &&
            (m_bAllowMultipleGeomFields ||
             m_poFeatureDefn->GetGeomFieldCount() == 0))
        {
            if (nColType == SQLITE_BLOB)
            {
                const int nBytes = sqlite3_column_bytes(hStmtIn, iCol);
                if (nBytes > 0)
                {
                    OGRSQLiteGeomFormat eGeomFormat = OSGF_None;
                    if (OGRIsBinaryGeomCol(hStmtIn, iCol, oField, eGeomFormat))
                    {
                        auto poGeomFieldDefn =
                            std::make_unique<OGRSQLiteGeomFieldDefn>(
                                pszFieldName, iCol);
                        poGeomFieldDefn->m_eGeomFormat = eGeomFormat;
                        m_poFeatureDefn->AddGeomFieldDefn(
                            std::move(poGeomFieldDefn));
                        continue;
                    }
                }
                else
                {
                    /* This could also be a SpatialLite geometry, so */
                    /* we'll also try to decode as SpatialLite if */
                    /* bTriedAsSpatiaLite is not FALSE */
                    auto poGeomFieldDefn =
                        std::make_unique<OGRSQLiteGeomFieldDefn>(pszFieldName,
                                                                 iCol);
                    poGeomFieldDefn->m_eGeomFormat = OSGF_WKB;
                    m_poFeatureDefn->AddGeomFieldDefn(
                        std::move(poGeomFieldDefn));
                    continue;
                }
            }
            else if (nColType == SQLITE_TEXT)
            {
                const char *pszText = reinterpret_cast<const char *>(
                    sqlite3_column_text(hStmtIn, iCol));
                if (pszText != nullptr)
                {
                    OGRSQLiteGeomFormat eGeomFormat = OSGF_None;
                    CPLPushErrorHandler(CPLQuietErrorHandler);

                    auto [poGeometry, eErr] =
                        OGRGeometryFactory::createFromWkt(pszText);
                    if (eErr == OGRERR_NONE)
                    {
                        eGeomFormat = OSGF_WKT;
                        auto poGeomFieldDefn =
                            std::make_unique<OGRSQLiteGeomFieldDefn>(
                                pszFieldName, iCol);
                        poGeomFieldDefn->m_eGeomFormat = eGeomFormat;
                        m_poFeatureDefn->AddGeomFieldDefn(
                            std::move(poGeomFieldDefn));
                    }
                    CPLPopErrorHandler();
                    CPLErrorReset();
                    if (eGeomFormat != OSGF_None)
                        continue;
                }
                else
                {
                    auto poGeomFieldDefn =
                        std::make_unique<OGRSQLiteGeomFieldDefn>(pszFieldName,
                                                                 iCol);
                    poGeomFieldDefn->m_eGeomFormat = OSGF_WKT;
                    m_poFeatureDefn->AddGeomFieldDefn(
                        std::move(poGeomFieldDefn));
                    continue;
                }
            }
        }
        else if (paosGeomCols == nullptr && nColType == SQLITE_NULL &&
                 !STARTS_WITH_CI(pszFieldName, "Gpkg") &&
                 !STARTS_WITH_CI(pszFieldName, "AsGPB(") &&
                 !STARTS_WITH_CI(pszFieldName, "CastAutomagic(") &&
                 OGRSQLiteIsSpatialFunctionReturningGeometry(pszFieldName))
        {
            auto poGeomFieldDefn =
                std::make_unique<OGRSQLiteGeomFieldDefn>(pszFieldName, iCol);
            poGeomFieldDefn->m_eGeomFormat = OSGF_SpatiaLite;
            m_poFeatureDefn->AddGeomFieldDefn(std::move(poGeomFieldDefn));
            continue;
        }

        // SpatialLite / Gaia
        if (paosGeomCols == nullptr && EQUAL(pszFieldName, "GaiaGeometry") &&
            (m_bAllowMultipleGeomFields ||
             m_poFeatureDefn->GetGeomFieldCount() == 0))
        {
            auto poGeomFieldDefn =
                std::make_unique<OGRSQLiteGeomFieldDefn>(pszFieldName, iCol);
            poGeomFieldDefn->m_eGeomFormat = OSGF_SpatiaLite;
            m_poFeatureDefn->AddGeomFieldDefn(std::move(poGeomFieldDefn));
            continue;
        }

        // Recognize a geometry column from trying to build the geometry
        // Useful for OGRSQLiteSelectLayer
        if (paosGeomCols == nullptr && nColType == SQLITE_BLOB &&
            (m_bAllowMultipleGeomFields ||
             m_poFeatureDefn->GetGeomFieldCount() == 0))
        {
            const int nBytes = sqlite3_column_bytes(hStmtIn, iCol);
            OGRSQLiteGeomFormat eGeomFormat = OSGF_None;
            if (nBytes > 0 &&
                OGRIsBinaryGeomCol(hStmtIn, iCol, oField, eGeomFormat))
            {
                auto poGeomFieldDefn = std::make_unique<OGRSQLiteGeomFieldDefn>(
                    pszFieldName, iCol);
                poGeomFieldDefn->m_eGeomFormat = eGeomFormat;
                m_poFeatureDefn->AddGeomFieldDefn(std::move(poGeomFieldDefn));
                continue;
            }
        }

        // The rowid is for internal use, not a real column.
        if (EQUAL(pszFieldName, "_rowid_"))
            continue;

        // The OGC_FID is for internal use, not a real user visible column.
        if (EQUAL(pszFieldName, "OGC_FID"))
            continue;

        /* Config option just in case we would not want that in some cases */
        if ((eFieldType == OFTTime || eFieldType == OFTDate ||
             eFieldType == OFTDateTime) &&
            CPLTestBool(
                CPLGetConfigOption("OGR_SQLITE_ENABLE_DATETIME", "YES")))
        {
            oField.SetType(eFieldType);
        }

        oSetFields.insert(oField.GetNameRef());
        m_poFeatureDefn->AddFieldDefn(&oField);
        m_panFieldOrdinals[m_poFeatureDefn->GetFieldCount() - 1] = iCol;
    }

    if (m_pszFIDColumn != nullptr)
    {
        for (int iCol = 0; iCol < nRawColumns; iCol++)
        {
            if (EQUAL(SQLUnescape(sqlite3_column_name(hStmtIn, iCol)).c_str(),
                      m_pszFIDColumn))
            {
                m_iFIDCol = iCol;
                break;
            }
        }
    }
}

/************************************************************************/
/*                            GetFIDColumn()                            */
/************************************************************************/

const char *OGRSQLiteLayer::GetFIDColumn()

{
    GetLayerDefn();
    if (m_pszFIDColumn != nullptr)
        return m_pszFIDColumn;
    else
        return "";
}

/************************************************************************/
/*                            ResetReading()                            */
/************************************************************************/

void OGRSQLiteLayer::ResetReading()

{
    ClearStatement();
    m_iNextShapeId = 0;
    m_bEOF = false;
}

/************************************************************************/
/*                           GetNextFeature()                           */
/************************************************************************/

OGRFeature *OGRSQLiteLayer::GetNextFeature()

{
    if (m_bEOF)
        return nullptr;

    while (true)
    {
        OGRFeature *poFeature = GetNextRawFeature();
        if (poFeature == nullptr)
        {
            m_bEOF = true;
            return nullptr;
        }

        if ((m_poFilterGeom == nullptr ||
             FilterGeometry(poFeature->GetGeomFieldRef(m_iGeomFieldFilter))) &&
            (m_poAttrQuery == nullptr || m_poAttrQuery->Evaluate(poFeature)))
            return poFeature;

        delete poFeature;
    }
}

/************************************************************************/
/*                         GetNextRawFeature()                          */
/************************************************************************/

OGRFeature *OGRSQLiteLayer::GetNextRawFeature()

{
    if (m_hStmt == nullptr)
    {
        ResetStatement();
        if (m_hStmt == nullptr)
            return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Fetch a record (unless otherwise instructed)                    */
    /* -------------------------------------------------------------------- */
    if (m_bDoStep)
    {
        const int rc = sqlite3_step(m_hStmt);
        if (rc != SQLITE_ROW)
        {
            if (rc != SQLITE_DONE)
            {
                sqlite3_reset(m_hStmt);
                CPLError(CE_Failure, CPLE_AppDefined,
                         "In GetNextRawFeature(): sqlite3_step() : %s",
                         sqlite3_errmsg(m_poDS->GetDB()));
            }

            ClearStatement();

            return nullptr;
        }
    }
    else
    {
        m_bDoStep = true;
    }

    /* -------------------------------------------------------------------- */
    /*      Create a feature from the current result.                       */
    /* -------------------------------------------------------------------- */
    OGRFeature *poFeature = new OGRFeature(m_poFeatureDefn);

    /* -------------------------------------------------------------------- */
    /*      Set FID if we have a column to set it from.                     */
    /* -------------------------------------------------------------------- */
    if (m_iFIDCol >= 0)
        poFeature->SetFID(sqlite3_column_int64(m_hStmt, m_iFIDCol));
    else
        poFeature->SetFID(m_iNextShapeId);

    m_iNextShapeId++;

    m_nFeaturesRead++;

    /* -------------------------------------------------------------------- */
    /*      Process Geometry if we have a column.                           */
    /* -------------------------------------------------------------------- */
    for (int iField = 0; iField < m_poFeatureDefn->GetGeomFieldCount();
         iField++)
    {
        OGRSQLiteGeomFieldDefn *poGeomFieldDefn =
            m_poFeatureDefn->myGetGeomFieldDefn(iField);
        if (!poGeomFieldDefn->IsIgnored())
        {
            OGRGeometry *poGeometry = nullptr;
            if (poGeomFieldDefn->m_eGeomFormat == OSGF_WKT)
            {
                const char *pszWKT = reinterpret_cast<const char *>(
                    sqlite3_column_text(m_hStmt, poGeomFieldDefn->m_iCol));
                OGRGeometryFactory::createFromWkt(pszWKT, nullptr, &poGeometry);
            }
            else if (poGeomFieldDefn->m_eGeomFormat == OSGF_WKB)
            {
                const int nBytes =
                    sqlite3_column_bytes(m_hStmt, poGeomFieldDefn->m_iCol);

                /* Try as spatialite first since createFromWkb() can sometimes
                 */
                /* interpret spatialite blobs as WKB for certain SRID values */
                if (!poGeomFieldDefn->m_bTriedAsSpatiaLite)
                {
                    /* If the layer is the result of a sql select, we cannot be
                     * sure if it is */
                    /* WKB or SpatialLite format */
                    // coverity[tainted_data_return]
                    const GByte *pabyBlob = reinterpret_cast<const GByte *>(
                        sqlite3_column_blob(m_hStmt, poGeomFieldDefn->m_iCol));
                    if (ImportSpatiaLiteGeometry(pabyBlob, nBytes,
                                                 &poGeometry) == OGRERR_NONE)
                    {
                        poGeomFieldDefn->m_eGeomFormat = OSGF_SpatiaLite;
                    }
                    poGeomFieldDefn->m_bTriedAsSpatiaLite = true;
                }

                if (poGeomFieldDefn->m_eGeomFormat == OSGF_WKB)
                {
                    // coverity[tainted_data_return]
                    const void *pabyBlob =
                        sqlite3_column_blob(m_hStmt, poGeomFieldDefn->m_iCol);
                    CPL_IGNORE_RET_VAL(OGRGeometryFactory::createFromWkb(
                        pabyBlob, nullptr, &poGeometry, nBytes));
                }
            }
            else if (poGeomFieldDefn->m_eGeomFormat == OSGF_FGF)
            {
                const int nBytes =
                    sqlite3_column_bytes(m_hStmt, poGeomFieldDefn->m_iCol);
                // coverity[tainted_data_return]
                const void *pabyBlob =
                    sqlite3_column_blob(m_hStmt, poGeomFieldDefn->m_iCol);
                OGRGeometryFactory::createFromFgf(pabyBlob, nullptr,
                                                  &poGeometry, nBytes, nullptr);
            }
            else if (poGeomFieldDefn->m_eGeomFormat == OSGF_SpatiaLite)
            {
                const int nBytes =
                    sqlite3_column_bytes(m_hStmt, poGeomFieldDefn->m_iCol);
                // coverity[tainted_data_return]
                const GByte *pabyBlob = reinterpret_cast<const GByte *>(
                    sqlite3_column_blob(m_hStmt, poGeomFieldDefn->m_iCol));
                CPL_IGNORE_RET_VAL(
                    ImportSpatiaLiteGeometry(pabyBlob, nBytes, &poGeometry));
            }

            if (poGeometry != nullptr)
            {
                if (poGeomFieldDefn->GetSpatialRef() != nullptr)
                    poGeometry->assignSpatialReference(
                        poGeomFieldDefn->GetSpatialRef());
                poFeature->SetGeomFieldDirectly(iField, poGeometry);
            }
        }
    }

    /* -------------------------------------------------------------------- */
    /*      set the fields.                                                 */
    /* -------------------------------------------------------------------- */
    const int nFieldCount = m_poFeatureDefn->GetFieldCount();
    for (int iField = 0; iField < nFieldCount; iField++)
    {
        const OGRFieldDefn *poFieldDefn =
            m_poFeatureDefn->GetFieldDefnUnsafe(iField);
        if (poFieldDefn->IsIgnored())
            continue;

        int iRawField = m_panFieldOrdinals[iField];

        int nSQLite3Type = sqlite3_column_type(m_hStmt, iRawField);
        if (nSQLite3Type == SQLITE_NULL)
        {
            poFeature->SetFieldNull(iField);
            continue;
        }

        switch (poFieldDefn->GetType())
        {
            case OFTInteger:
            {
                /* Possible since SQLite3 has no strong typing */
                if (nSQLite3Type == SQLITE_TEXT)
                    poFeature->SetField(
                        iField, reinterpret_cast<const char *>(
                                    sqlite3_column_text(m_hStmt, iRawField)));
                else
                {
                    const GIntBig nVal =
                        sqlite3_column_int64(m_hStmt, iRawField);
                    if (poFieldDefn->GetSubType() == OFSTBoolean)
                    {
                        poFeature->SetField(iField, nVal != 0);
                    }
                    else
                    {
                        if (nVal >= INT_MIN && nVal <= INT_MAX)
                            poFeature->SetFieldSameTypeUnsafe(
                                iField, static_cast<int>(nVal));
                        else
                            poFeature->SetField(iField, nVal);
                    }
                }
                break;
            }

            case OFTInteger64:
            {
                /* Possible since SQLite3 has no strong typing */
                if (nSQLite3Type == SQLITE_TEXT)
                    poFeature->SetField(
                        iField, reinterpret_cast<const char *>(
                                    sqlite3_column_text(m_hStmt, iRawField)));
                else
                    poFeature->SetFieldSameTypeUnsafe(
                        iField, sqlite3_column_int64(m_hStmt, iRawField));
                break;
            }

            case OFTReal:
            {
                /* Possible since SQLite3 has no strong typing */
                if (nSQLite3Type == SQLITE_TEXT)
                    poFeature->SetField(
                        iField, reinterpret_cast<const char *>(
                                    sqlite3_column_text(m_hStmt, iRawField)));
                else
                    poFeature->SetField(
                        iField, sqlite3_column_double(m_hStmt, iRawField));
                break;
            }

            case OFTBinary:
            {
                const int nBytes = sqlite3_column_bytes(m_hStmt, iRawField);
                // coverity[tainted_data_return]
                const GByte *pabyData = reinterpret_cast<const GByte *>(
                    sqlite3_column_blob(m_hStmt, iRawField));
                poFeature->SetField(iField, nBytes,
                                    const_cast<GByte *>(pabyData));
            }
            break;

            case OFTString:
            case OFTIntegerList:
            case OFTInteger64List:
            case OFTRealList:
            case OFTStringList:
            {
                if (CSLFindString(
                        m_papszCompressedColumns,
                        m_poFeatureDefn->GetFieldDefn(iField)->GetNameRef()) >=
                    0)
                {
                    const int nBytes = sqlite3_column_bytes(m_hStmt, iRawField);
                    // coverity[tainted_data_return]
                    const GByte *pabyBlob = reinterpret_cast<const GByte *>(
                        sqlite3_column_blob(m_hStmt, iRawField));

                    void *pOut =
                        CPLZLibInflate(pabyBlob, nBytes, nullptr, 0, nullptr);
                    if (pOut != nullptr)
                    {
                        poFeature->SetField(iField,
                                            static_cast<const char *>(pOut));
                        CPLFree(pOut);
                    }
                    else
                    {
                        poFeature->SetField(
                            iField,
                            reinterpret_cast<const char *>(
                                sqlite3_column_text(m_hStmt, iRawField)));
                    }
                }
                else
                {
                    poFeature->SetField(
                        iField, reinterpret_cast<const char *>(
                                    sqlite3_column_text(m_hStmt, iRawField)));
                }
                break;
            }

            case OFTDate:
            case OFTTime:
            case OFTDateTime:
            {
                if (sqlite3_column_type(m_hStmt, iRawField) == SQLITE_TEXT)
                {
                    const char *pszValue = reinterpret_cast<const char *>(
                        sqlite3_column_text(m_hStmt, iRawField));
                    if (!OGRParseDate(pszValue,
                                      poFeature->GetRawFieldRef(iField), 0))
                        poFeature->UnsetField(iField);
                }
                else if (sqlite3_column_type(m_hStmt, iRawField) ==
                         SQLITE_FLOAT)
                {
                    // Try converting from Julian day
                    char **papszResult = nullptr;
                    sqlite3_get_table(
                        m_poDS->GetDB(),
                        CPLSPrintf(
                            "SELECT strftime('%%Y-%%m-%%d %%H:%%M:%%S', %.16g)",
                            sqlite3_column_double(m_hStmt, iRawField)),
                        &papszResult, nullptr, nullptr, nullptr);
                    if (papszResult && papszResult[0] && papszResult[1])
                    {
                        if (!OGRParseDate(papszResult[1],
                                          poFeature->GetRawFieldRef(iField), 0))
                            poFeature->UnsetField(iField);
                    }
                    sqlite3_free_table(papszResult);
                }
                break;
            }

            default:
                break;
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Set native data if found                                        */
    /* -------------------------------------------------------------------- */
    if (m_iOGRNativeDataCol >= 0 &&
        sqlite3_column_type(m_hStmt, m_iOGRNativeDataCol) == SQLITE_TEXT)
    {
        poFeature->SetNativeData(reinterpret_cast<const char *>(
            sqlite3_column_text(m_hStmt, m_iOGRNativeDataCol)));
    }
    if (m_iOGRNativeMediaTypeCol >= 0 &&
        sqlite3_column_type(m_hStmt, m_iOGRNativeMediaTypeCol) == SQLITE_TEXT)
    {
        poFeature->SetNativeMediaType(reinterpret_cast<const char *>(
            sqlite3_column_text(m_hStmt, m_iOGRNativeMediaTypeCol)));
    }

    return poFeature;
}

/************************************************************************/
/*                             GetFeature()                             */
/************************************************************************/

OGRFeature *OGRSQLiteLayer::GetFeature(GIntBig nFeatureId)

{
    return OGRLayer::GetFeature(nFeatureId);
}

/************************************************************************/
/*                     createFromSpatialiteInternal()                   */
/************************************************************************/

/* See http://www.gaia-gis.it/spatialite/spatialite-manual-2.3.0.html#t3.3 */
/* for the specification of the spatialite BLOB geometry format */
/* Derived from WKB, but unfortunately it is not practical to reuse existing */
/* WKB encoding/decoding code */

#ifdef CPL_LSB
#define NEED_SWAP_SPATIALITE() (eByteOrder != wkbNDR)
#else
#define NEED_SWAP_SPATIALITE() (eByteOrder == wkbNDR)
#endif

OGRErr OGRSQLiteLayer::createFromSpatialiteInternal(
    const GByte *pabyData, OGRGeometry **ppoReturn, int nBytes,
    OGRwkbByteOrder eByteOrder, int *pnBytesConsumed, int nRecLevel)
{
    *ppoReturn = nullptr;

    /* Arbitrary value, but certainly large enough for reasonable usages ! */
    if (nRecLevel == 32)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Too many recursion levels (%d) while parsing "
                 "Spatialite geometry.",
                 nRecLevel);
        return OGRERR_CORRUPT_DATA;
    }

    if (nBytes < 4)
        return OGRERR_NOT_ENOUGH_DATA;

    /* -------------------------------------------------------------------- */
    /*      Decode the geometry type.                                       */
    /* -------------------------------------------------------------------- */
    GInt32 nGType = 0;
    memcpy(&nGType, pabyData, 4);
    if (NEED_SWAP_SPATIALITE())
        CPL_SWAP32PTR(&nGType);

    if ((nGType >= OGRSplitePointXY &&
         nGType <= OGRSpliteGeometryCollectionXY) ||  // XY types
        (nGType >= OGRSplitePointXYZ &&
         nGType <= OGRSpliteGeometryCollectionXYZ) ||  // XYZ types
        (nGType >= OGRSplitePointXYM &&
         nGType <= OGRSpliteGeometryCollectionXYM) ||  // XYM types
        (nGType >= OGRSplitePointXYZM &&
         nGType <= OGRSpliteGeometryCollectionXYZM) ||  // XYZM types
        (nGType >= OGRSpliteComprLineStringXY &&
         nGType <= OGRSpliteComprGeometryCollectionXY) ||  // XY compressed
        (nGType >= OGRSpliteComprLineStringXYZ &&
         nGType <= OGRSpliteComprGeometryCollectionXYZ) ||  // XYZ compressed
        (nGType >= OGRSpliteComprLineStringXYM &&
         nGType <= OGRSpliteComprGeometryCollectionXYM) ||  // XYM compressed
        (nGType >= OGRSpliteComprLineStringXYZM &&
         nGType <= OGRSpliteComprGeometryCollectionXYZM))  // XYZM compressed
        ;
    else
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;

    /* -------------------------------------------------------------------- */
    /*      Point [XY]                                                      */
    /* -------------------------------------------------------------------- */
    OGRGeometry *poGeom = nullptr;
    GInt32 compressedSize = 0;

    if (nGType == OGRSplitePointXY)
    {
        if (nBytes < 4 + 2 * 8)
            return OGRERR_NOT_ENOUGH_DATA;

        double adfTuple[2] = {0.0, 0.0};

        memcpy(adfTuple, pabyData + 4, 2 * 8);
        if (NEED_SWAP_SPATIALITE())
        {
            CPL_SWAP64PTR(adfTuple);
            CPL_SWAP64PTR(adfTuple + 1);
        }

        poGeom = new OGRPoint(adfTuple[0], adfTuple[1]);

        if (pnBytesConsumed)
            *pnBytesConsumed = 4 + 2 * 8;
    }
    /* -------------------------------------------------------------------- */
    /*      Point [XYZ]                                                     */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSplitePointXYZ)
    {
        if (nBytes < 4 + 3 * 8)
            return OGRERR_NOT_ENOUGH_DATA;

        double adfTuple[3] = {0.0, 0.0, 0.0};

        memcpy(adfTuple, pabyData + 4, 3 * 8);
        if (NEED_SWAP_SPATIALITE())
        {
            CPL_SWAP64PTR(adfTuple);
            CPL_SWAP64PTR(adfTuple + 1);
            CPL_SWAP64PTR(adfTuple + 2);
        }

        poGeom = new OGRPoint(adfTuple[0], adfTuple[1], adfTuple[2]);

        if (pnBytesConsumed)
            *pnBytesConsumed = 4 + 3 * 8;
    }

    /* -------------------------------------------------------------------- */
    /*      Point [XYM]                                                     */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSplitePointXYM)
    {
        if (nBytes < 4 + 3 * 8)
            return OGRERR_NOT_ENOUGH_DATA;

        double adfTuple[3] = {0.0, 0.0, 0.0};
        memcpy(adfTuple, pabyData + 4, 3 * 8);
        if (NEED_SWAP_SPATIALITE())
        {
            CPL_SWAP64PTR(adfTuple);
            CPL_SWAP64PTR(adfTuple + 1);
            CPL_SWAP64PTR(adfTuple + 2);
        }

        OGRPoint *poPoint = new OGRPoint(adfTuple[0], adfTuple[1]);
        poPoint->setM(adfTuple[2]);
        poGeom = poPoint;

        if (pnBytesConsumed)
            *pnBytesConsumed = 4 + 3 * 8;
    }

    /* -------------------------------------------------------------------- */
    /*      Point [XYZM]                                                    */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSplitePointXYZM)
    {
        if (nBytes < 4 + 4 * 8)
            return OGRERR_NOT_ENOUGH_DATA;

        double adfTuple[4];
        memcpy(adfTuple, pabyData + 4, 4 * 8);
        if (NEED_SWAP_SPATIALITE())
        {
            CPL_SWAP64PTR(adfTuple);
            CPL_SWAP64PTR(adfTuple + 1);
            CPL_SWAP64PTR(adfTuple + 2);
            CPL_SWAP64PTR(adfTuple + 3);
        }

        poGeom =
            new OGRPoint(adfTuple[0], adfTuple[1], adfTuple[2], adfTuple[3]);

        if (pnBytesConsumed)
            *pnBytesConsumed = 4 + 4 * 8;
    }

    /* -------------------------------------------------------------------- */
    /*      LineString [XY]                                                 */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSpliteLineStringXY)
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nPointCount = 0;
        memcpy(&nPointCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nPointCount);

        if (nPointCount < 0 || nPointCount > INT_MAX / (2 * 8))
            return OGRERR_CORRUPT_DATA;

        if (nBytes - 8 < 2 * 8 * nPointCount)
            return OGRERR_NOT_ENOUGH_DATA;

        OGRLineString *poLS = new OGRLineString();
        poGeom = poLS;
        if (!NEED_SWAP_SPATIALITE())
        {
            poLS->setPoints(nPointCount,
                            reinterpret_cast<const OGRRawPoint *>(pabyData + 8),
                            nullptr);
        }
        else
        {
            poLS->setNumPoints(nPointCount, FALSE);
            for (int iPoint = 0; iPoint < nPointCount; iPoint++)
            {
                double adfTuple[2] = {0.0, 0.0};
                memcpy(adfTuple, pabyData + 8 + 2 * 8 * iPoint, 2 * 8);
                CPL_SWAP64PTR(adfTuple);
                CPL_SWAP64PTR(adfTuple + 1);
                poLS->setPoint(iPoint, adfTuple[0], adfTuple[1]);
            }
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = 8 + 2 * 8 * nPointCount;
    }

    /* -------------------------------------------------------------------- */
    /*      LineString [XYZ]                                                */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSpliteLineStringXYZ)
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nPointCount = 0;
        memcpy(&nPointCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nPointCount);

        if (nPointCount < 0 || nPointCount > INT_MAX / (3 * 8))
            return OGRERR_CORRUPT_DATA;

        if (nBytes - 8 < 3 * 8 * nPointCount)
            return OGRERR_NOT_ENOUGH_DATA;

        OGRLineString *poLS = new OGRLineString();
        poGeom = poLS;
        poLS->setNumPoints(nPointCount);

        for (int iPoint = 0; iPoint < nPointCount; iPoint++)
        {
            double adfTuple[3] = {0.0, 0.0, 0.0};
            memcpy(adfTuple, pabyData + 8 + 3 * 8 * iPoint, 3 * 8);
            if (NEED_SWAP_SPATIALITE())
            {
                CPL_SWAP64PTR(adfTuple);
                CPL_SWAP64PTR(adfTuple + 1);
                CPL_SWAP64PTR(adfTuple + 2);
            }

            poLS->setPoint(iPoint, adfTuple[0], adfTuple[1], adfTuple[2]);
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = 8 + 3 * 8 * nPointCount;
    }

    /* -------------------------------------------------------------------- */
    /*      LineString [XYM]                                                */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSpliteLineStringXYM)
    {

        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nPointCount = 0;
        memcpy(&nPointCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nPointCount);

        if (nPointCount < 0 || nPointCount > INT_MAX / (3 * 8))
            return OGRERR_CORRUPT_DATA;

        if (nBytes - 8 < 3 * 8 * nPointCount)
            return OGRERR_NOT_ENOUGH_DATA;

        OGRLineString *poLS = new OGRLineString();
        poGeom = poLS;
        poLS->setNumPoints(nPointCount);

        for (int iPoint = 0; iPoint < nPointCount; iPoint++)
        {
            double adfTuple[3] = {0.0, 0.0, 0.0};
            memcpy(adfTuple, pabyData + 8 + 3 * 8 * iPoint, 3 * 8);
            if (NEED_SWAP_SPATIALITE())
            {
                CPL_SWAP64PTR(adfTuple);
                CPL_SWAP64PTR(adfTuple + 1);
                CPL_SWAP64PTR(adfTuple + 2);
            }

            poLS->setPointM(iPoint, adfTuple[0], adfTuple[1], adfTuple[2]);
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = 8 + 3 * 8 * nPointCount;
    }

    /* -------------------------------------------------------------------- */
    /*      LineString [XYZM]                                               */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSpliteLineStringXYZM)
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nPointCount = 0;
        memcpy(&nPointCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nPointCount);

        if (nPointCount < 0 || nPointCount > INT_MAX / (4 * 8))
            return OGRERR_CORRUPT_DATA;

        if (nBytes - 8 < 4 * 8 * nPointCount)
            return OGRERR_NOT_ENOUGH_DATA;

        OGRLineString *poLS = new OGRLineString();
        poGeom = poLS;
        poLS->setNumPoints(nPointCount);

        for (int iPoint = 0; iPoint < nPointCount; iPoint++)
        {
            double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
            memcpy(adfTuple, pabyData + 8 + 4 * 8 * iPoint, 4 * 8);
            if (NEED_SWAP_SPATIALITE())
            {
                CPL_SWAP64PTR(adfTuple);
                CPL_SWAP64PTR(adfTuple + 1);
                CPL_SWAP64PTR(adfTuple + 2);
                CPL_SWAP64PTR(adfTuple + 3);
            }

            poLS->setPoint(iPoint, adfTuple[0], adfTuple[1], adfTuple[2],
                           adfTuple[3]);
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = 8 + 4 * 8 * nPointCount;
    }

    /* -------------------------------------------------------------------- */
    /*      LineString [XY] Compressed                                      */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSpliteComprLineStringXY)
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nPointCount = 0;
        memcpy(&nPointCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nPointCount);

        if (nPointCount < 0 || nPointCount - 2 > (INT_MAX - 16 * 2) / 8)
            return OGRERR_CORRUPT_DATA;

        compressedSize = 16 * 2;                  // first and last Points
        compressedSize += 8 * (nPointCount - 2);  // intermediate Points

        if (nBytes - 8 < compressedSize)
            return OGRERR_NOT_ENOUGH_DATA;

        OGRLineString *poLS = new OGRLineString();
        poGeom = poLS;
        poLS->setNumPoints(nPointCount);

        int nNextByte = 8;
        double adfTupleBase[2] = {0.0, 0.0};

        for (int iPoint = 0; iPoint < nPointCount; iPoint++)
        {
            double adfTuple[2] = {0.0, 0.0};

            if (iPoint == 0 || iPoint == (nPointCount - 1))
            {
                // first and last Points are uncompressed
                memcpy(adfTuple, pabyData + nNextByte, 2 * 8);
                nNextByte += 2 * 8;

                if (NEED_SWAP_SPATIALITE())
                {
                    CPL_SWAP64PTR(adfTuple);
                    CPL_SWAP64PTR(adfTuple + 1);
                }
            }
            else
            {
                // any other intermediate Point is compressed
                float asfTuple[2] = {0.0f, 0.0f};
                memcpy(asfTuple, pabyData + nNextByte, 2 * 4);
                nNextByte += 2 * 4;

                if (NEED_SWAP_SPATIALITE())
                {
                    CPL_SWAP32PTR(asfTuple);
                    CPL_SWAP32PTR(asfTuple + 1);
                }
                adfTuple[0] = asfTuple[0] + adfTupleBase[0];
                adfTuple[1] = asfTuple[1] + adfTupleBase[1];
            }

            poLS->setPoint(iPoint, adfTuple[0], adfTuple[1]);
            adfTupleBase[0] = adfTuple[0];
            adfTupleBase[1] = adfTuple[1];
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = nNextByte;
    }

    /* -------------------------------------------------------------------- */
    /*      LineString [XYZ] Compressed                                     */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSpliteComprLineStringXYZ)
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nPointCount = 0;
        memcpy(&nPointCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nPointCount);

        if (nPointCount < 0 || nPointCount - 2 > (INT_MAX - 24 * 2) / 12)
            return OGRERR_CORRUPT_DATA;

        compressedSize = 24 * 2;                   // first and last Points
        compressedSize += 12 * (nPointCount - 2);  // intermediate Points

        if (nBytes - 8 < compressedSize)
            return OGRERR_NOT_ENOUGH_DATA;

        OGRLineString *poLS = new OGRLineString();
        poGeom = poLS;
        poLS->setNumPoints(nPointCount);

        int nNextByte = 8;
        double adfTupleBase[3] = {0.0, 0.0, 0.0};

        for (int iPoint = 0; iPoint < nPointCount; iPoint++)
        {
            double adfTuple[3] = {0.0, 0.0, 0.0};

            if (iPoint == 0 || iPoint == (nPointCount - 1))
            {
                // first and last Points are uncompressed
                memcpy(adfTuple, pabyData + nNextByte, 3 * 8);
                nNextByte += 3 * 8;

                if (NEED_SWAP_SPATIALITE())
                {
                    CPL_SWAP64PTR(adfTuple);
                    CPL_SWAP64PTR(adfTuple + 1);
                    CPL_SWAP64PTR(adfTuple + 2);
                }
            }
            else
            {
                // any other intermediate Point is compressed
                float asfTuple[3] = {0.0f, 0.0f, 0.0f};
                memcpy(asfTuple, pabyData + nNextByte, 3 * 4);
                nNextByte += 3 * 4;

                if (NEED_SWAP_SPATIALITE())
                {
                    CPL_SWAP32PTR(asfTuple);
                    CPL_SWAP32PTR(asfTuple + 1);
                    CPL_SWAP32PTR(asfTuple + 2);
                }
                adfTuple[0] = asfTuple[0] + adfTupleBase[0];
                adfTuple[1] = asfTuple[1] + adfTupleBase[1];
                adfTuple[2] = asfTuple[2] + adfTupleBase[2];
            }

            poLS->setPoint(iPoint, adfTuple[0], adfTuple[1], adfTuple[2]);
            adfTupleBase[0] = adfTuple[0];
            adfTupleBase[1] = adfTuple[1];
            adfTupleBase[2] = adfTuple[2];
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = nNextByte;
    }

    /* -------------------------------------------------------------------- */
    /*      LineString [XYM] Compressed                                     */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSpliteComprLineStringXYM)
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nPointCount = 0;
        memcpy(&nPointCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nPointCount);

        if (nPointCount < 0 || nPointCount - 2 > (INT_MAX - 24 * 2) / 16)
            return OGRERR_CORRUPT_DATA;

        compressedSize = 24 * 2;                   // first and last Points
        compressedSize += 16 * (nPointCount - 2);  // intermediate Points

        if (nBytes - 8 < compressedSize)
            return OGRERR_NOT_ENOUGH_DATA;

        OGRLineString *poLS = new OGRLineString();
        poGeom = poLS;
        poLS->setNumPoints(nPointCount);

        int nNextByte = 8;
        double adfTupleBase[2] = {0.0, 0.0};

        for (int iPoint = 0; iPoint < nPointCount; iPoint++)
        {
            double adfTuple[3] = {0.0, 0.0, 0.0};
            if (iPoint == 0 || iPoint == (nPointCount - 1))
            {
                // first and last Points are uncompressed
                memcpy(adfTuple, pabyData + nNextByte, 3 * 8);
                nNextByte += 3 * 8;

                if (NEED_SWAP_SPATIALITE())
                {
                    CPL_SWAP64PTR(adfTuple);
                    CPL_SWAP64PTR(adfTuple + 1);
                    CPL_SWAP64PTR(adfTuple + 2);
                }
            }
            else
            {
                // any other intermediate Point is compressed
                float asfTuple[2] = {0.0f, 0.0f};
                memcpy(asfTuple, pabyData + nNextByte, 2 * 4);
                memcpy(adfTuple + 2, pabyData + nNextByte + 2 * 4, 8);
                nNextByte += 2 * 4 + 8;

                if (NEED_SWAP_SPATIALITE())
                {
                    CPL_SWAP32PTR(asfTuple);
                    CPL_SWAP32PTR(asfTuple + 1);
                    CPL_SWAP64PTR(
                        adfTuple +
                        2); /* adfTuple and not asfTuple is intended */
                }
                adfTuple[0] = asfTuple[0] + adfTupleBase[0];
                adfTuple[1] = asfTuple[1] + adfTupleBase[1];
            }

            poLS->setPointM(iPoint, adfTuple[0], adfTuple[1], adfTuple[2]);
            adfTupleBase[0] = adfTuple[0];
            adfTupleBase[1] = adfTuple[1];
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = nNextByte;
    }

    /* -------------------------------------------------------------------- */
    /*      LineString [XYZM] Compressed                                    */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSpliteComprLineStringXYZM)
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nPointCount = 0;
        memcpy(&nPointCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nPointCount);

        if (nPointCount < 0 || nPointCount - 2 > (INT_MAX - 32 * 2) / 20)
            return OGRERR_CORRUPT_DATA;

        compressedSize = 32 * 2;  // first and last Points
        /* Note 20 is not an error : x,y,z are float and the m is a double */
        compressedSize += 20 * (nPointCount - 2);  // intermediate Points

        if (nBytes - 8 < compressedSize)
            return OGRERR_NOT_ENOUGH_DATA;

        OGRLineString *poLS = new OGRLineString();
        poGeom = poLS;
        poLS->setNumPoints(nPointCount);

        int nNextByte = 8;
        double adfTupleBase[3] = {0.0, 0.0, 0.0};

        for (int iPoint = 0; iPoint < nPointCount; iPoint++)
        {
            double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};

            if (iPoint == 0 || iPoint == (nPointCount - 1))
            {
                // first and last Points are uncompressed
                memcpy(adfTuple, pabyData + nNextByte, 4 * 8);
                nNextByte += 4 * 8;

                if (NEED_SWAP_SPATIALITE())
                {
                    CPL_SWAP64PTR(adfTuple);
                    CPL_SWAP64PTR(adfTuple + 1);
                    CPL_SWAP64PTR(adfTuple + 2);
                    CPL_SWAP64PTR(adfTuple + 3);
                }
            }
            else
            {
                // any other intermediate Point is compressed
                float asfTuple[3] = {0.0f, 0.0f, 0.0f};
                memcpy(asfTuple, pabyData + nNextByte, 3 * 4);
                memcpy(adfTuple + 3, pabyData + nNextByte + 3 * 4, 8);
                nNextByte += 3 * 4 + 8;

                if (NEED_SWAP_SPATIALITE())
                {
                    CPL_SWAP32PTR(asfTuple);
                    CPL_SWAP32PTR(asfTuple + 1);
                    CPL_SWAP32PTR(asfTuple + 2);
                    CPL_SWAP64PTR(
                        adfTuple +
                        3); /* adfTuple and not asfTuple is intended */
                }
                adfTuple[0] = asfTuple[0] + adfTupleBase[0];
                adfTuple[1] = asfTuple[1] + adfTupleBase[1];
                adfTuple[2] = asfTuple[2] + adfTupleBase[2];
            }

            poLS->setPoint(iPoint, adfTuple[0], adfTuple[1], adfTuple[2],
                           adfTuple[3]);
            adfTupleBase[0] = adfTuple[0];
            adfTupleBase[1] = adfTuple[1];
            adfTupleBase[2] = adfTuple[2];
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = nNextByte;
    }

    /* -------------------------------------------------------------------- */
    /*      Polygon [XY]                                                    */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSplitePolygonXY)
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nRingCount = 0;
        memcpy(&nRingCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nRingCount);

        if (nRingCount < 0 || nRingCount > INT_MAX / 4)
            return OGRERR_CORRUPT_DATA;

        // Each ring has a minimum of 4 bytes
        if (nBytes - 8 < nRingCount * 4)
            return OGRERR_NOT_ENOUGH_DATA;

        int nNextByte = 8;

        OGRPolygon *poPoly = new OGRPolygon();
        poGeom = poPoly;

        for (int iRing = 0; iRing < nRingCount; iRing++)
        {
            if (nBytes - nNextByte < 4)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            GInt32 nPointCount = 0;
            memcpy(&nPointCount, pabyData + nNextByte, 4);
            if (NEED_SWAP_SPATIALITE())
                CPL_SWAP32PTR(&nPointCount);

            if (nPointCount < 0 || nPointCount > INT_MAX / (2 * 8))
            {
                delete poPoly;
                return OGRERR_CORRUPT_DATA;
            }

            nNextByte += 4;

            if (nBytes - nNextByte < 2 * 8 * nPointCount)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            OGRLinearRing *poLR = new OGRLinearRing();
            if (!NEED_SWAP_SPATIALITE())
            {
                poLR->setPoints(
                    nPointCount,
                    reinterpret_cast<const OGRRawPoint *>(pabyData + nNextByte),
                    nullptr);
                nNextByte += 2 * 8 * nPointCount;
            }
            else
            {
                poLR->setNumPoints(nPointCount, FALSE);
                for (int iPoint = 0; iPoint < nPointCount; iPoint++)
                {
                    double adfTuple[2] = {0.0, 0.0};
                    memcpy(adfTuple, pabyData + nNextByte, 2 * 8);
                    nNextByte += 2 * 8;
                    CPL_SWAP64PTR(adfTuple);
                    CPL_SWAP64PTR(adfTuple + 1);
                    poLR->setPoint(iPoint, adfTuple[0], adfTuple[1]);
                }
            }

            poPoly->addRingDirectly(poLR);
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = nNextByte;
    }

    /* -------------------------------------------------------------------- */
    /*      Polygon [XYZ]                                                   */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSplitePolygonXYZ)
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nRingCount = 0;
        memcpy(&nRingCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nRingCount);

        if (nRingCount < 0 || nRingCount > INT_MAX / 4)
            return OGRERR_CORRUPT_DATA;

        // Each ring has a minimum of 4 bytes
        if (nBytes - 8 < nRingCount * 4)
            return OGRERR_NOT_ENOUGH_DATA;

        int nNextByte = 8;

        OGRPolygon *poPoly = new OGRPolygon();
        poGeom = poPoly;

        for (int iRing = 0; iRing < nRingCount; iRing++)
        {
            if (nBytes - nNextByte < 4)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            GInt32 nPointCount = 0;
            memcpy(&nPointCount, pabyData + nNextByte, 4);
            if (NEED_SWAP_SPATIALITE())
                CPL_SWAP32PTR(&nPointCount);

            if (nPointCount < 0 || nPointCount > INT_MAX / (3 * 8))
            {
                delete poPoly;
                return OGRERR_CORRUPT_DATA;
            }

            nNextByte += 4;

            if (nBytes - nNextByte < 3 * 8 * nPointCount)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            OGRLinearRing *poLR = new OGRLinearRing();
            poLR->setNumPoints(nPointCount, FALSE);

            for (int iPoint = 0; iPoint < nPointCount; iPoint++)
            {
                double adfTuple[3] = {0.0, 0.0, 0.0};
                memcpy(adfTuple, pabyData + nNextByte, 3 * 8);
                nNextByte += 3 * 8;

                if (NEED_SWAP_SPATIALITE())
                {
                    CPL_SWAP64PTR(adfTuple);
                    CPL_SWAP64PTR(adfTuple + 1);
                    CPL_SWAP64PTR(adfTuple + 2);
                }

                poLR->setPoint(iPoint, adfTuple[0], adfTuple[1], adfTuple[2]);
            }

            poPoly->addRingDirectly(poLR);
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = nNextByte;
    }

    /* -------------------------------------------------------------------- */
    /*      Polygon [XYM]                                                   */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSplitePolygonXYM)
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nRingCount = 0;
        memcpy(&nRingCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nRingCount);

        if (nRingCount < 0 || nRingCount > INT_MAX / 4)
            return OGRERR_CORRUPT_DATA;

        // Each ring has a minimum of 4 bytes
        if (nBytes - 8 < nRingCount * 4)
            return OGRERR_NOT_ENOUGH_DATA;

        int nNextByte = 8;

        OGRPolygon *poPoly = new OGRPolygon();
        poGeom = poPoly;

        for (int iRing = 0; iRing < nRingCount; iRing++)
        {
            if (nBytes - nNextByte < 4)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            GInt32 nPointCount = 0;
            memcpy(&nPointCount, pabyData + nNextByte, 4);
            if (NEED_SWAP_SPATIALITE())
                CPL_SWAP32PTR(&nPointCount);

            if (nPointCount < 0 || nPointCount > INT_MAX / (3 * 8))
            {
                delete poPoly;
                return OGRERR_CORRUPT_DATA;
            }

            nNextByte += 4;

            if (nBytes - nNextByte < 3 * 8 * nPointCount)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            OGRLinearRing *poLR = new OGRLinearRing();
            poLR->setNumPoints(nPointCount, FALSE);

            for (int iPoint = 0; iPoint < nPointCount; iPoint++)
            {
                double adfTuple[3] = {0.0, 0.0, 0.0};
                memcpy(adfTuple, pabyData + nNextByte, 3 * 8);
                nNextByte += 3 * 8;

                if (NEED_SWAP_SPATIALITE())
                {
                    CPL_SWAP64PTR(adfTuple);
                    CPL_SWAP64PTR(adfTuple + 1);
                    CPL_SWAP64PTR(adfTuple + 2);
                }

                poLR->setPointM(iPoint, adfTuple[0], adfTuple[1], adfTuple[2]);
            }

            poPoly->addRingDirectly(poLR);
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = nNextByte;
    }

    /* -------------------------------------------------------------------- */
    /*      Polygon [XYZM]                                                  */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSplitePolygonXYZM)
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nRingCount = 0;
        memcpy(&nRingCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nRingCount);

        if (nRingCount < 0 || nRingCount > INT_MAX / 4)
            return OGRERR_CORRUPT_DATA;

        // Each ring has a minimum of 4 bytes
        if (nBytes - 8 < nRingCount * 4)
            return OGRERR_NOT_ENOUGH_DATA;

        int nNextByte = 8;

        OGRPolygon *poPoly = new OGRPolygon();
        poGeom = poPoly;

        for (int iRing = 0; iRing < nRingCount; iRing++)
        {
            if (nBytes - nNextByte < 4)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            GInt32 nPointCount = 0;
            memcpy(&nPointCount, pabyData + nNextByte, 4);
            if (NEED_SWAP_SPATIALITE())
                CPL_SWAP32PTR(&nPointCount);

            if (nPointCount < 0 || nPointCount > INT_MAX / (4 * 8))
            {
                delete poPoly;
                return OGRERR_CORRUPT_DATA;
            }

            nNextByte += 4;

            if (nBytes - nNextByte < 4 * 8 * nPointCount)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            OGRLinearRing *poLR = new OGRLinearRing();
            poLR->setNumPoints(nPointCount, FALSE);

            for (int iPoint = 0; iPoint < nPointCount; iPoint++)
            {
                double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};

                memcpy(adfTuple, pabyData + nNextByte, 4 * 8);
                nNextByte += 4 * 8;

                if (NEED_SWAP_SPATIALITE())
                {
                    CPL_SWAP64PTR(adfTuple);
                    CPL_SWAP64PTR(adfTuple + 1);
                    CPL_SWAP64PTR(adfTuple + 2);
                    CPL_SWAP64PTR(adfTuple + 3);
                }

                poLR->setPoint(iPoint, adfTuple[0], adfTuple[1], adfTuple[2],
                               adfTuple[3]);
            }

            poPoly->addRingDirectly(poLR);
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = nNextByte;
    }

    /* -------------------------------------------------------------------- */
    /*      Polygon [XY] Compressed                                         */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSpliteComprPolygonXY)
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nRingCount = 0;
        memcpy(&nRingCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nRingCount);

        if (nRingCount < 0 || nRingCount > INT_MAX / 4)
            return OGRERR_CORRUPT_DATA;

        // Each ring has a minimum of 4 bytes
        if (nBytes - 8 < nRingCount * 4)
            return OGRERR_NOT_ENOUGH_DATA;

        int nNextByte = 8;

        OGRPolygon *poPoly = new OGRPolygon();
        poGeom = poPoly;

        for (int iRing = 0; iRing < nRingCount; iRing++)
        {
            if (nBytes - nNextByte < 4)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            GInt32 nPointCount = 0;
            memcpy(&nPointCount, pabyData + nNextByte, 4);
            if (NEED_SWAP_SPATIALITE())
                CPL_SWAP32PTR(&nPointCount);

            if (nPointCount < 0 || nPointCount - 2 > (INT_MAX - 16 * 2) / 8)
            {
                delete poPoly;
                return OGRERR_CORRUPT_DATA;
            }

            compressedSize = 16 * 2;                  // first and last Points
            compressedSize += 8 * (nPointCount - 2);  // intermediate Points

            nNextByte += 4;

            if (nBytes - nNextByte < compressedSize)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            double adfTupleBase[2] = {0.0, 0.0};
            OGRLinearRing *poLR = new OGRLinearRing();
            poLR->setNumPoints(nPointCount, FALSE);

            for (int iPoint = 0; iPoint < nPointCount; iPoint++)
            {
                double adfTuple[2] = {0.0, 0.0};
                if (iPoint == 0 || iPoint == (nPointCount - 1))
                {
                    // first and last Points are uncompressed
                    memcpy(adfTuple, pabyData + nNextByte, 2 * 8);
                    nNextByte += 2 * 8;

                    if (NEED_SWAP_SPATIALITE())
                    {
                        CPL_SWAP64PTR(adfTuple);
                        CPL_SWAP64PTR(adfTuple + 1);
                    }
                }
                else
                {
                    // any other intermediate Point is compressed
                    float asfTuple[2] = {0.0f, 0.0f};
                    memcpy(asfTuple, pabyData + nNextByte, 2 * 4);
                    nNextByte += 2 * 4;

                    if (NEED_SWAP_SPATIALITE())
                    {
                        CPL_SWAP32PTR(asfTuple);
                        CPL_SWAP32PTR(asfTuple + 1);
                    }
                    adfTuple[0] = asfTuple[0] + adfTupleBase[0];
                    adfTuple[1] = asfTuple[1] + adfTupleBase[1];
                }

                poLR->setPoint(iPoint, adfTuple[0], adfTuple[1]);
                adfTupleBase[0] = adfTuple[0];
                adfTupleBase[1] = adfTuple[1];
            }

            poPoly->addRingDirectly(poLR);
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = nNextByte;
    }

    /* -------------------------------------------------------------------- */
    /*      Polygon [XYZ] Compressed                                        */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSpliteComprPolygonXYZ)
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nRingCount = 0;
        memcpy(&nRingCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nRingCount);

        if (nRingCount < 0 || nRingCount > INT_MAX / 4)
            return OGRERR_CORRUPT_DATA;

        // Each ring has a minimum of 4 bytes
        if (nBytes - 8 < nRingCount * 4)
            return OGRERR_NOT_ENOUGH_DATA;

        int nNextByte = 8;

        OGRPolygon *poPoly = new OGRPolygon();
        poGeom = poPoly;

        for (int iRing = 0; iRing < nRingCount; iRing++)
        {
            if (nBytes - nNextByte < 4)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            GInt32 nPointCount = 0;
            memcpy(&nPointCount, pabyData + nNextByte, 4);
            if (NEED_SWAP_SPATIALITE())
                CPL_SWAP32PTR(&nPointCount);

            if (nPointCount < 0 || nPointCount - 2 > (INT_MAX - 24 * 2) / 12)
            {
                delete poPoly;
                return OGRERR_CORRUPT_DATA;
            }

            compressedSize = 24 * 2;                   // first and last Points
            compressedSize += 12 * (nPointCount - 2);  // intermediate Points

            nNextByte += 4;

            if (nBytes - nNextByte < compressedSize)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            double adfTupleBase[3] = {0.0, 0.0, 0.0};
            OGRLinearRing *poLR = new OGRLinearRing();
            poLR->setNumPoints(nPointCount, FALSE);

            for (int iPoint = 0; iPoint < nPointCount; iPoint++)
            {
                double adfTuple[3] = {0.0, 0.0, 0.0};
                if (iPoint == 0 || iPoint == (nPointCount - 1))
                {
                    // first and last Points are uncompressed
                    memcpy(adfTuple, pabyData + nNextByte, 3 * 8);
                    nNextByte += 3 * 8;

                    if (NEED_SWAP_SPATIALITE())
                    {
                        CPL_SWAP64PTR(adfTuple);
                        CPL_SWAP64PTR(adfTuple + 1);
                        CPL_SWAP64PTR(adfTuple + 2);
                    }
                }
                else
                {
                    // any other intermediate Point is compressed
                    float asfTuple[3] = {0.0, 0.0, 0.0};
                    memcpy(asfTuple, pabyData + nNextByte, 3 * 4);
                    nNextByte += 3 * 4;

                    if (NEED_SWAP_SPATIALITE())
                    {
                        CPL_SWAP32PTR(asfTuple);
                        CPL_SWAP32PTR(asfTuple + 1);
                        CPL_SWAP32PTR(asfTuple + 2);
                    }
                    adfTuple[0] = asfTuple[0] + adfTupleBase[0];
                    adfTuple[1] = asfTuple[1] + adfTupleBase[1];
                    adfTuple[2] = asfTuple[2] + adfTupleBase[2];
                }

                poLR->setPoint(iPoint, adfTuple[0], adfTuple[1], adfTuple[2]);
                adfTupleBase[0] = adfTuple[0];
                adfTupleBase[1] = adfTuple[1];
                adfTupleBase[2] = adfTuple[2];
            }

            poPoly->addRingDirectly(poLR);
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = nNextByte;
    }

    /* -------------------------------------------------------------------- */
    /*      Polygon [XYM] Compressed                                        */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSpliteComprPolygonXYM)
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nRingCount = 0;
        memcpy(&nRingCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nRingCount);

        if (nRingCount < 0 || nRingCount > INT_MAX / 4)
            return OGRERR_CORRUPT_DATA;

        // Each ring has a minimum of 4 bytes
        if (nBytes - 8 < nRingCount * 4)
            return OGRERR_NOT_ENOUGH_DATA;

        int nNextByte = 8;

        OGRPolygon *poPoly = new OGRPolygon();
        poGeom = poPoly;

        for (int iRing = 0; iRing < nRingCount; iRing++)
        {
            if (nBytes - nNextByte < 4)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            GInt32 nPointCount = 0;
            memcpy(&nPointCount, pabyData + nNextByte, 4);
            if (NEED_SWAP_SPATIALITE())
                CPL_SWAP32PTR(&nPointCount);

            if (nPointCount < 0 || nPointCount - 2 > (INT_MAX - 24 * 2) / 16)
            {
                delete poPoly;
                return OGRERR_CORRUPT_DATA;
            }

            compressedSize = 24 * 2;                   // first and last Points
            compressedSize += 16 * (nPointCount - 2);  // intermediate Points

            nNextByte += 4;

            if (nBytes - nNextByte < compressedSize)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            double adfTupleBase[2] = {0.0, 0.0};
            OGRLinearRing *poLR = new OGRLinearRing();
            poLR->setNumPoints(nPointCount, FALSE);

            for (int iPoint = 0; iPoint < nPointCount; iPoint++)
            {
                double adfTuple[3] = {0.0, 0.0, 0.0};
                if (iPoint == 0 || iPoint == (nPointCount - 1))
                {
                    // first and last Points are uncompressed
                    memcpy(adfTuple, pabyData + nNextByte, 3 * 8);
                    nNextByte += 3 * 8;

                    if (NEED_SWAP_SPATIALITE())
                    {
                        CPL_SWAP64PTR(adfTuple);
                        CPL_SWAP64PTR(adfTuple + 1);
                        CPL_SWAP64PTR(adfTuple + 2);
                    }
                }
                else
                {
                    // any other intermediate Point is compressed
                    float asfTuple[2] = {0.0f, 0.0f};
                    memcpy(asfTuple, pabyData + nNextByte, 2 * 4);
                    memcpy(adfTuple + 2, pabyData + nNextByte + 2 * 4, 8);
                    nNextByte += 2 * 4 + 8;

                    if (NEED_SWAP_SPATIALITE())
                    {
                        CPL_SWAP32PTR(asfTuple);
                        CPL_SWAP32PTR(asfTuple + 1);
                        CPL_SWAP64PTR(
                            adfTuple +
                            2); /* adfTuple and not asfTuple is intended */
                    }
                    adfTuple[0] = asfTuple[0] + adfTupleBase[0];
                    adfTuple[1] = asfTuple[1] + adfTupleBase[1];
                }

                poLR->setPointM(iPoint, adfTuple[0], adfTuple[1], adfTuple[2]);
                adfTupleBase[0] = adfTuple[0];
                adfTupleBase[1] = adfTuple[1];
            }

            poPoly->addRingDirectly(poLR);
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = nNextByte;
    }

    /* -------------------------------------------------------------------- */
    /*      Polygon [XYZM] Compressed                                       */
    /* -------------------------------------------------------------------- */
    else if (nGType == OGRSpliteComprPolygonXYZM)
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nRingCount = 0;
        memcpy(&nRingCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nRingCount);

        if (nRingCount < 0 || nRingCount > INT_MAX / 4)
            return OGRERR_CORRUPT_DATA;

        // Each ring has a minimum of 4 bytes
        if (nBytes - 8 < nRingCount * 4)
            return OGRERR_NOT_ENOUGH_DATA;

        int nNextByte = 8;

        OGRPolygon *poPoly = new OGRPolygon();
        poGeom = poPoly;

        for (int iRing = 0; iRing < nRingCount; iRing++)
        {
            if (nBytes - nNextByte < 4)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            GInt32 nPointCount = 0;
            memcpy(&nPointCount, pabyData + nNextByte, 4);
            if (NEED_SWAP_SPATIALITE())
                CPL_SWAP32PTR(&nPointCount);

            if (nPointCount < 0 || nPointCount - 2 > (INT_MAX - 32 * 2) / 20)
            {
                delete poPoly;
                return OGRERR_CORRUPT_DATA;
            }

            compressedSize = 32 * 2;  // first and last Points
            /* Note 20 is not an error : x,y,z are float and the m is a double
             */
            compressedSize += 20 * (nPointCount - 2);  // intermediate Points

            nNextByte += 4;

            if (nBytes - nNextByte < compressedSize)
            {
                delete poPoly;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            double adfTupleBase[3] = {0.0, 0.0, 0.0};
            OGRLinearRing *poLR = new OGRLinearRing();
            poLR->setNumPoints(nPointCount, FALSE);

            for (int iPoint = 0; iPoint < nPointCount; iPoint++)
            {
                double adfTuple[4] = {0.0, 0.0, 0.0, 0.0};
                if (iPoint == 0 || iPoint == (nPointCount - 1))
                {
                    // first and last Points are uncompressed
                    memcpy(adfTuple, pabyData + nNextByte, 4 * 8);
                    nNextByte += 4 * 8;

                    if (NEED_SWAP_SPATIALITE())
                    {
                        CPL_SWAP64PTR(adfTuple);
                        CPL_SWAP64PTR(adfTuple + 1);
                        CPL_SWAP64PTR(adfTuple + 2);
                        CPL_SWAP64PTR(adfTuple + 3);
                    }
                }
                else
                {
                    // any other intermediate Point is compressed
                    float asfTuple[3] = {0.0f, 0.0f, 0.0f};
                    memcpy(asfTuple, pabyData + nNextByte, 3 * 4);
                    memcpy(adfTuple + 3, pabyData + nNextByte + 3 * 4, 8);
                    nNextByte += 3 * 4 + 8;

                    if (NEED_SWAP_SPATIALITE())
                    {
                        CPL_SWAP32PTR(asfTuple);
                        CPL_SWAP32PTR(asfTuple + 1);
                        CPL_SWAP32PTR(asfTuple + 2);
                        CPL_SWAP64PTR(
                            adfTuple +
                            3); /* adfTuple and not asfTuple is intended */
                    }
                    adfTuple[0] = asfTuple[0] + adfTupleBase[0];
                    adfTuple[1] = asfTuple[1] + adfTupleBase[1];
                    adfTuple[2] = asfTuple[2] + adfTupleBase[2];
                }

                poLR->setPoint(iPoint, adfTuple[0], adfTuple[1], adfTuple[2],
                               adfTuple[3]);
                adfTupleBase[0] = adfTuple[0];
                adfTupleBase[1] = adfTuple[1];
                adfTupleBase[2] = adfTuple[2];
            }

            poPoly->addRingDirectly(poLR);
        }

        if (pnBytesConsumed)
            *pnBytesConsumed = nNextByte;
    }

    /* -------------------------------------------------------------------- */
    /*      GeometryCollections of various kinds.                           */
    /* -------------------------------------------------------------------- */
    else if ((nGType >= OGRSpliteMultiPointXY &&
              nGType <= OGRSpliteGeometryCollectionXY) ||  // XY types
             (nGType >= OGRSpliteMultiPointXYZ &&
              nGType <= OGRSpliteGeometryCollectionXYZ) ||  // XYZ types
             (nGType >= OGRSpliteMultiPointXYM &&
              nGType <= OGRSpliteGeometryCollectionXYM) ||  // XYM types
             (nGType >= OGRSpliteMultiPointXYZM &&
              nGType <= OGRSpliteGeometryCollectionXYZM) ||  // XYZM types
             (nGType >= OGRSpliteComprMultiLineStringXY &&
              nGType <= OGRSpliteComprGeometryCollectionXY) ||  // XY compressed
             (nGType >= OGRSpliteComprMultiLineStringXYZ &&
              nGType <=
                  OGRSpliteComprGeometryCollectionXYZ) ||  // XYZ compressed
             (nGType >= OGRSpliteComprMultiLineStringXYM &&
              nGType <=
                  OGRSpliteComprGeometryCollectionXYM) ||  // XYM compressed
             (nGType >= OGRSpliteComprMultiLineStringXYZM &&
              nGType <=
                  OGRSpliteComprGeometryCollectionXYZM))  // XYZM compressed
    {
        if (nBytes < 8)
            return OGRERR_NOT_ENOUGH_DATA;

        GInt32 nGeomCount = 0;
        memcpy(&nGeomCount, pabyData + 4, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nGeomCount);

        if (nGeomCount < 0 || nGeomCount > INT_MAX / 9)
            return OGRERR_CORRUPT_DATA;

        // Each sub geometry takes at least 9 bytes
        if (nBytes - 8 < nGeomCount * 9)
            return OGRERR_NOT_ENOUGH_DATA;

        int nBytesUsed = 8;
        OGRGeometryCollection *poGC = nullptr;

        switch (nGType)
        {
            case OGRSpliteMultiPointXY:
            case OGRSpliteMultiPointXYZ:
            case OGRSpliteMultiPointXYM:
            case OGRSpliteMultiPointXYZM:
                poGC = new OGRMultiPoint();
                break;
            case OGRSpliteMultiLineStringXY:
            case OGRSpliteMultiLineStringXYZ:
            case OGRSpliteMultiLineStringXYM:
            case OGRSpliteMultiLineStringXYZM:
            case OGRSpliteComprMultiLineStringXY:
            case OGRSpliteComprMultiLineStringXYZ:
            case OGRSpliteComprMultiLineStringXYM:
            case OGRSpliteComprMultiLineStringXYZM:
                poGC = new OGRMultiLineString();
                break;
            case OGRSpliteMultiPolygonXY:
            case OGRSpliteMultiPolygonXYZ:
            case OGRSpliteMultiPolygonXYM:
            case OGRSpliteMultiPolygonXYZM:
            case OGRSpliteComprMultiPolygonXY:
            case OGRSpliteComprMultiPolygonXYZ:
            case OGRSpliteComprMultiPolygonXYM:
            case OGRSpliteComprMultiPolygonXYZM:
                poGC = new OGRMultiPolygon();
                break;
            case OGRSpliteGeometryCollectionXY:
            case OGRSpliteGeometryCollectionXYZ:
            case OGRSpliteGeometryCollectionXYM:
            case OGRSpliteGeometryCollectionXYZM:
            case OGRSpliteComprGeometryCollectionXY:
            case OGRSpliteComprGeometryCollectionXYZ:
            case OGRSpliteComprGeometryCollectionXYM:
            case OGRSpliteComprGeometryCollectionXYZM:
                poGC = new OGRGeometryCollection();
                break;
        }

        assert(nullptr != poGC);

        for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
        {
            OGRGeometry *poThisGeom = nullptr;

            if (nBytes - nBytesUsed < 5)
            {
                delete poGC;
                return OGRERR_NOT_ENOUGH_DATA;
            }

            if (pabyData[nBytesUsed] != 0x69)
            {
                delete poGC;
                return OGRERR_CORRUPT_DATA;
            }

            nBytesUsed++;

            int nThisGeomSize = 0;
            OGRErr eErr = createFromSpatialiteInternal(
                pabyData + nBytesUsed, &poThisGeom, nBytes - nBytesUsed,
                eByteOrder, &nThisGeomSize, nRecLevel + 1);
            if (eErr != OGRERR_NONE)
            {
                delete poGC;
                return eErr;
            }

            nBytesUsed += nThisGeomSize;
            eErr = poGC->addGeometryDirectly(poThisGeom);
            if (eErr != OGRERR_NONE)
            {
                delete poThisGeom;
                delete poGC;
                return eErr;
            }
        }

        poGeom = poGC;
        if (pnBytesConsumed)
            *pnBytesConsumed = nBytesUsed;
    }

    /* -------------------------------------------------------------------- */
    /*      Currently unsupported geometry.                                 */
    /* -------------------------------------------------------------------- */
    else
    {
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
    }

    *ppoReturn = poGeom;
    return OGRERR_NONE;
}

/************************************************************************/
/*                     GetSpatialiteGeometryHeader()                    */
/************************************************************************/
typedef struct
{
    int nSpatialiteType;
    OGRwkbGeometryType eGType;
} SpatialiteOGRGeometryTypeTuple;

static const SpatialiteOGRGeometryTypeTuple anTypesMap[] = {
    {OGRSplitePointXY, wkbPoint},
    {OGRSplitePointXYZ, wkbPoint25D},
    {OGRSplitePointXYM, wkbPointM},
    {OGRSplitePointXYZM, wkbPointZM},
    {OGRSpliteLineStringXY, wkbLineString},
    {OGRSpliteLineStringXYZ, wkbLineString25D},
    {OGRSpliteLineStringXYM, wkbLineStringM},
    {OGRSpliteLineStringXYZM, wkbLineStringZM},
    {OGRSpliteComprLineStringXY, wkbLineString},
    {OGRSpliteComprLineStringXYZ, wkbLineString25D},
    {OGRSpliteComprLineStringXYM, wkbLineStringM},
    {OGRSpliteComprLineStringXYZM, wkbLineStringZM},
    {OGRSplitePolygonXY, wkbPolygon},
    {OGRSplitePolygonXYZ, wkbPolygon25D},
    {OGRSplitePolygonXYM, wkbPolygonM},
    {OGRSplitePolygonXYZM, wkbPolygonZM},
    {OGRSpliteComprPolygonXY, wkbPolygon},
    {OGRSpliteComprPolygonXYZ, wkbPolygon25D},
    {OGRSpliteComprPolygonXYM, wkbPolygonM},
    {OGRSpliteComprPolygonXYZM, wkbPolygonZM},

    {OGRSpliteMultiPointXY, wkbMultiPoint},
    {OGRSpliteMultiPointXYZ, wkbMultiPoint25D},
    {OGRSpliteMultiPointXYM, wkbMultiPointM},
    {OGRSpliteMultiPointXYZM, wkbMultiPointZM},
    {OGRSpliteMultiLineStringXY, wkbMultiLineString},
    {OGRSpliteMultiLineStringXYZ, wkbMultiLineString25D},
    {OGRSpliteMultiLineStringXYM, wkbMultiLineStringM},
    {OGRSpliteMultiLineStringXYZM, wkbMultiLineStringZM},
    {OGRSpliteComprMultiLineStringXY, wkbMultiLineString},
    {OGRSpliteComprMultiLineStringXYZ, wkbMultiLineString25D},
    {OGRSpliteComprMultiLineStringXYM, wkbMultiLineStringM},
    {OGRSpliteComprMultiLineStringXYZM, wkbMultiLineStringZM},
    {OGRSpliteMultiPolygonXY, wkbMultiPolygon},
    {OGRSpliteMultiPolygonXYZ, wkbMultiPolygon25D},
    {OGRSpliteMultiPolygonXYM, wkbMultiPolygonM},
    {OGRSpliteMultiPolygonXYZM, wkbMultiPolygonZM},
    {OGRSpliteComprMultiPolygonXY, wkbMultiPolygon},
    {OGRSpliteComprMultiPolygonXYZ, wkbMultiPolygon25D},
    {OGRSpliteComprMultiPolygonXYM, wkbMultiPolygonM},
    {OGRSpliteComprMultiPolygonXYZM, wkbMultiPolygonZM},

    {OGRSpliteGeometryCollectionXY, wkbGeometryCollection},
    {OGRSpliteGeometryCollectionXYZ, wkbGeometryCollection25D},
    {OGRSpliteGeometryCollectionXYM, wkbGeometryCollectionM},
    {OGRSpliteGeometryCollectionXYZM, wkbGeometryCollectionZM},
    {OGRSpliteComprGeometryCollectionXY, wkbGeometryCollection},
    {OGRSpliteComprGeometryCollectionXYZ, wkbGeometryCollection25D},
    {OGRSpliteComprGeometryCollectionXYM, wkbGeometryCollectionM},
    {OGRSpliteComprGeometryCollectionXYZM, wkbGeometryCollectionZM},
};

static bool QuickCheckForSpatialiteGeometryValidity(const GByte *pabyData,
                                                    int nBytes)
{
    return nBytes >= 44 && pabyData[0] == 0 &&
           (pabyData[1] == wkbXDR || pabyData[1] == wkbNDR) &&
           pabyData[38] == 0x7C && pabyData[nBytes - 1] == 0xFE;
}

OGRErr OGRSQLiteLayer::GetSpatialiteGeometryHeader(
    const GByte *pabyData, int nBytes, int *pnSRID, OGRwkbGeometryType *peType,
    bool *pbIsEmpty, double *pdfMinX, double *pdfMinY, double *pdfMaxX,
    double *pdfMaxY)
{
    if (!QuickCheckForSpatialiteGeometryValidity(pabyData, nBytes))
        return OGRERR_CORRUPT_DATA;

    const OGRwkbByteOrder eByteOrder =
        static_cast<OGRwkbByteOrder>(pabyData[1]);

    if (pnSRID != nullptr)
    {
        int nSRID = 0;
        memcpy(&nSRID, pabyData + 2, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nSRID);
        *pnSRID = nSRID;
    }

    if (peType != nullptr || pbIsEmpty != nullptr)
    {
        OGRwkbGeometryType eGType = wkbUnknown;
        int nSpatialiteType = 0;
        memcpy(&nSpatialiteType, pabyData + 39, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nSpatialiteType);
        for (size_t i = 0; i < CPL_ARRAYSIZE(anTypesMap); ++i)
        {
            if (anTypesMap[i].nSpatialiteType == nSpatialiteType)
            {
                eGType = anTypesMap[i].eGType;
                break;
            }
        }
        if (peType != nullptr)
            *peType = eGType;
        if (pbIsEmpty != nullptr)
        {
            *pbIsEmpty = false;
            if (wkbFlatten(eGType) != wkbPoint && nBytes >= 44 + 4)
            {
                int nCount = 0;
                memcpy(&nCount, pabyData + 43, 4);
                if (NEED_SWAP_SPATIALITE())
                    CPL_SWAP32PTR(&nCount);
                *pbIsEmpty = (nCount == 0);
            }
        }
    }

    if (pdfMinX != nullptr)
    {
        double dfMinX = 0.0;
        memcpy(&dfMinX, pabyData + 6, 8);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP64PTR(&dfMinX);
        *pdfMinX = dfMinX;
    }

    if (pdfMinY != nullptr)
    {
        double dfMinY = 0.0;
        memcpy(&dfMinY, pabyData + 14, 8);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP64PTR(&dfMinY);
        *pdfMinY = dfMinY;
    }

    if (pdfMaxX != nullptr)
    {
        double dfMaxX = 0.0;
        memcpy(&dfMaxX, pabyData + 22, 8);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP64PTR(&dfMaxX);
        *pdfMaxX = dfMaxX;
    }

    if (pdfMaxY != nullptr)
    {
        double dfMaxY = 0.0;
        memcpy(&dfMaxY, pabyData + 30, 8);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP64PTR(&dfMaxY);
        *pdfMaxY = dfMaxY;
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                      ImportSpatiaLiteGeometry()                      */
/************************************************************************/

OGRErr OGRSQLiteLayer::ImportSpatiaLiteGeometry(const GByte *pabyData,
                                                int nBytes,
                                                OGRGeometry **ppoGeometry)

{
    return ImportSpatiaLiteGeometry(pabyData, nBytes, ppoGeometry, nullptr);
}

/************************************************************************/
/*                      ImportSpatiaLiteGeometry()                      */
/************************************************************************/

OGRErr OGRSQLiteLayer::ImportSpatiaLiteGeometry(const GByte *pabyData,
                                                int nBytes,
                                                OGRGeometry **ppoGeometry,
                                                int *pnSRID)

{
    *ppoGeometry = nullptr;

    if (!QuickCheckForSpatialiteGeometryValidity(pabyData, nBytes))
        return OGRERR_CORRUPT_DATA;

    const OGRwkbByteOrder eByteOrder =
        static_cast<OGRwkbByteOrder>(pabyData[1]);

    /* -------------------------------------------------------------------- */
    /*      Decode the geometry type.                                       */
    /* -------------------------------------------------------------------- */
    if (pnSRID != nullptr)
    {
        int nSRID = 0;
        memcpy(&nSRID, pabyData + 2, 4);
        if (NEED_SWAP_SPATIALITE())
            CPL_SWAP32PTR(&nSRID);
        *pnSRID = nSRID;
    }

    int nBytesConsumed = 0;
    OGRErr eErr =
        createFromSpatialiteInternal(pabyData + 39, ppoGeometry, nBytes - 39,
                                     eByteOrder, &nBytesConsumed, 0);
    if (eErr == OGRERR_NONE)
    {
        /* This is a hack: in OGR2SQLITE_ExportGeometry(), we may have added */
        /* the original curve geometry after the spatialite blob, so in case */
        /* we detect that there's still binary */
        /* content after the spatialite blob, this may be our original geometry
         */
        if (39 + nBytesConsumed + 1 < nBytes &&
            pabyData[39 + nBytesConsumed] == 0xFE)
        {
            OGRGeometry *poOriginalGeometry = nullptr;
            eErr = OGRGeometryFactory::createFromWkb(
                pabyData + 39 + nBytesConsumed + 1, nullptr,
                &poOriginalGeometry, nBytes - (39 + nBytesConsumed + 1 + 1));
            delete *ppoGeometry;
            if (eErr == OGRERR_NONE)
            {
                *ppoGeometry = poOriginalGeometry;
            }
            else
            {
                *ppoGeometry = nullptr;
            }
        }
    }
    return eErr;
}

/************************************************************************/
/*                CanBeCompressedSpatialiteGeometry()                   */
/************************************************************************/

int OGRSQLiteLayer::CanBeCompressedSpatialiteGeometry(
    const OGRGeometry *poGeometry)
{
    switch (wkbFlatten(poGeometry->getGeometryType()))
    {
        case wkbLineString:
        case wkbLinearRing:
        {
            int nPoints = poGeometry->toLineString()->getNumPoints();
            return nPoints >= 2;
        }

        case wkbPolygon:
        {
            const OGRPolygon *poPoly = poGeometry->toPolygon();
            if (poPoly->getExteriorRing() != nullptr)
            {
                if (!CanBeCompressedSpatialiteGeometry(
                        poPoly->getExteriorRing()))
                    return FALSE;

                int nInteriorRingCount = poPoly->getNumInteriorRings();
                for (int i = 0; i < nInteriorRingCount; i++)
                {
                    if (!CanBeCompressedSpatialiteGeometry(
                            poPoly->getInteriorRing(i)))
                        return FALSE;
                }
            }
            return TRUE;
        }

        case wkbMultiPoint:
        case wkbMultiLineString:
        case wkbMultiPolygon:
        case wkbGeometryCollection:
        {
            const OGRGeometryCollection *poGeomCollection =
                poGeometry->toGeometryCollection();
            int nParts = poGeomCollection->getNumGeometries();
            for (int i = 0; i < nParts; i++)
            {
                if (!CanBeCompressedSpatialiteGeometry(
                        poGeomCollection->getGeometryRef(i)))
                    return FALSE;
            }
            return TRUE;
        }

        default:
            return FALSE;
    }
}

/************************************************************************/
/*                        collectSimpleGeometries()                     */
/************************************************************************/

static void
collectSimpleGeometries(const OGRGeometryCollection *poGeomCollection,
                        std::vector<const OGRGeometry *> &simpleGeometries)
{
    const int nParts = poGeomCollection->getNumGeometries();
    simpleGeometries.reserve(simpleGeometries.size() + nParts);
    for (int i = 0; i < nParts; i++)
    {
        const OGRGeometry *poSubGeom = poGeomCollection->getGeometryRef(i);
        const OGRGeometryCollection *poSubGeomColl =
            dynamic_cast<const OGRGeometryCollection *>(poSubGeom);
        if (poSubGeomColl)
            collectSimpleGeometries(poSubGeomColl, simpleGeometries);
        else
            simpleGeometries.push_back(poSubGeom);
    }
}

/************************************************************************/
/*                  ComputeSpatiaLiteGeometrySize()                     */
/************************************************************************/

int OGRSQLiteLayer::ComputeSpatiaLiteGeometrySize(const OGRGeometry *poGeometry,
                                                  bool bSpatialite2D,
                                                  bool bUseComprGeom)
{
    switch (wkbFlatten(poGeometry->getGeometryType()))
    {
        case wkbPoint:
            if (bSpatialite2D == true)
                return 16;
            return 8 * poGeometry->CoordinateDimension();

        case wkbLineString:
        case wkbLinearRing:
        {
            int nPoints = poGeometry->toLineString()->getNumPoints();
            int nDimension = 2;
            int nPointsDouble = nPoints;
            int nPointsFloat = 0;
            bool bHasM = CPL_TO_BOOL(poGeometry->IsMeasured());
            if (bSpatialite2D == true)
            {
                // nDimension = 2;
                bHasM = false;
            }
            else
            {
                if (bUseComprGeom && nPoints >= 2)
                {
                    nPointsDouble = 2;
                    nPointsFloat = nPoints - 2;
                }
                nDimension = poGeometry->Is3D() ? 3 : 2;
            }
            return 4 + nDimension * (8 * nPointsDouble + 4 * nPointsFloat) +
                   (bHasM ? nPoints * 8 : 0);
        }

        case wkbPolygon:
        {
            int nSize = 4;
            const OGRPolygon *poPoly = poGeometry->toPolygon();
            bUseComprGeom = bUseComprGeom && !bSpatialite2D &&
                            CanBeCompressedSpatialiteGeometry(poGeometry);
            if (poPoly->getExteriorRing() != nullptr)
            {
                nSize += ComputeSpatiaLiteGeometrySize(
                    poPoly->getExteriorRing(), bSpatialite2D, bUseComprGeom);

                int nInteriorRingCount = poPoly->getNumInteriorRings();
                for (int i = 0; i < nInteriorRingCount; i++)
                    nSize += ComputeSpatiaLiteGeometrySize(
                        poPoly->getInteriorRing(i), bSpatialite2D,
                        bUseComprGeom);
            }
            return nSize;
        }

        case wkbMultiPoint:
        case wkbMultiLineString:
        case wkbMultiPolygon:
        case wkbGeometryCollection:
        {
            int nSize = 4;
            const OGRGeometryCollection *poGeomCollection =
                poGeometry->toGeometryCollection();

            std::vector<const OGRGeometry *> simpleGeometries;
            collectSimpleGeometries(poGeomCollection, simpleGeometries);

            int nParts = static_cast<int>(simpleGeometries.size());
            for (int i = 0; i < nParts; i++)
                nSize += 5 + ComputeSpatiaLiteGeometrySize(simpleGeometries[i],
                                                           bSpatialite2D,
                                                           bUseComprGeom);
            return nSize;
        }

        default:
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Unexpected geometry type: %s",
                     OGRToOGCGeomType(poGeometry->getGeometryType()));
            return 0;
        }
    }
}

/************************************************************************/
/*                    GetSpatialiteGeometryCode()                       */
/************************************************************************/

int OGRSQLiteLayer::GetSpatialiteGeometryCode(const OGRGeometry *poGeometry,
                                              bool bSpatialite2D,
                                              bool bUseComprGeom,
                                              bool bAcceptMultiGeom)
{
    OGRwkbGeometryType eType = wkbFlatten(poGeometry->getGeometryType());
    switch (eType)
    {
        case wkbPoint:
            if (bSpatialite2D == true)
                return OGRSplitePointXY;
            else if (poGeometry->Is3D())
            {
                if (poGeometry->IsMeasured())
                    return OGRSplitePointXYZM;
                else
                    return OGRSplitePointXYZ;
            }
            else
            {
                if (poGeometry->IsMeasured())
                    return OGRSplitePointXYM;
                else
                    return OGRSplitePointXY;
            }
            break;

        case wkbLineString:
        case wkbLinearRing:
            if (bSpatialite2D == true)
                return OGRSpliteLineStringXY;
            else if (poGeometry->Is3D())
            {
                if (poGeometry->IsMeasured())
                    return (bUseComprGeom) ? OGRSpliteComprLineStringXYZM
                                           : OGRSpliteLineStringXYZM;
                else
                    return (bUseComprGeom) ? OGRSpliteComprLineStringXYZ
                                           : OGRSpliteLineStringXYZ;
            }
            else
            {
                if (poGeometry->IsMeasured())
                    return (bUseComprGeom) ? OGRSpliteComprLineStringXYM
                                           : OGRSpliteLineStringXYM;
                else
                    return (bUseComprGeom) ? OGRSpliteComprLineStringXY
                                           : OGRSpliteLineStringXY;
            }
            break;

        case wkbPolygon:
            if (bSpatialite2D == true)
                return OGRSplitePolygonXY;
            else if (poGeometry->Is3D())
            {
                if (poGeometry->IsMeasured())
                    return (bUseComprGeom) ? OGRSpliteComprPolygonXYZM
                                           : OGRSplitePolygonXYZM;
                else
                    return (bUseComprGeom) ? OGRSpliteComprPolygonXYZ
                                           : OGRSplitePolygonXYZ;
            }
            else
            {
                if (poGeometry->IsMeasured())
                    return (bUseComprGeom) ? OGRSpliteComprPolygonXYM
                                           : OGRSplitePolygonXYM;
                else
                    return (bUseComprGeom) ? OGRSpliteComprPolygonXY
                                           : OGRSplitePolygonXY;
            }
            break;

        default:
            break;
    }

    if (!bAcceptMultiGeom)
    {
        return 0;
    }

    switch (eType)
    {
        case wkbMultiPoint:
            if (bSpatialite2D == true)
                return OGRSpliteMultiPointXY;
            else if (poGeometry->Is3D())
            {
                if (poGeometry->IsMeasured())
                    return OGRSpliteMultiPointXYZM;
                else
                    return OGRSpliteMultiPointXYZ;
            }
            else
            {
                if (poGeometry->IsMeasured())
                    return OGRSpliteMultiPointXYM;
                else
                    return OGRSpliteMultiPointXY;
            }
            break;

        case wkbMultiLineString:
            if (bSpatialite2D == true)
                return OGRSpliteMultiLineStringXY;
            else if (poGeometry->Is3D())
            {
                if (poGeometry->IsMeasured())
                    return /*(bUseComprGeom) ? OGRSpliteComprMultiLineStringXYZM
                              :*/
                        OGRSpliteMultiLineStringXYZM;
                else
                    return /*(bUseComprGeom) ? OGRSpliteComprMultiLineStringXYZ
                              :*/
                        OGRSpliteMultiLineStringXYZ;
            }
            else
            {
                if (poGeometry->IsMeasured())
                    return /*(bUseComprGeom) ? OGRSpliteComprMultiLineStringXYM
                              :*/
                        OGRSpliteMultiLineStringXYM;
                else
                    return /*(bUseComprGeom) ? OGRSpliteComprMultiLineStringXY
                              :*/
                        OGRSpliteMultiLineStringXY;
            }
            break;

        case wkbMultiPolygon:
            if (bSpatialite2D == true)
                return OGRSpliteMultiPolygonXY;
            else if (poGeometry->Is3D())
            {
                if (poGeometry->IsMeasured())
                    return /*(bUseComprGeom) ? OGRSpliteComprMultiPolygonXYZM
                              :*/
                        OGRSpliteMultiPolygonXYZM;
                else
                    return /*(bUseComprGeom) ? OGRSpliteComprMultiPolygonXYZ :*/
                        OGRSpliteMultiPolygonXYZ;
            }
            else
            {
                if (poGeometry->IsMeasured())
                    return /*(bUseComprGeom) ? OGRSpliteComprMultiPolygonXYM :*/
                        OGRSpliteMultiPolygonXYM;
                else
                    return /*(bUseComprGeom) ? OGRSpliteComprMultiPolygonXY :*/
                        OGRSpliteMultiPolygonXY;
            }
            break;

        case wkbGeometryCollection:
            if (bSpatialite2D == true)
                return OGRSpliteGeometryCollectionXY;
            else if (poGeometry->Is3D())
            {
                if (poGeometry->IsMeasured())
                    return /*(bUseComprGeom) ?
                              OGRSpliteComprGeometryCollectionXYZM :*/
                        OGRSpliteGeometryCollectionXYZM;
                else
                    return /*(bUseComprGeom) ?
                              OGRSpliteComprGeometryCollectionXYZ :*/
                        OGRSpliteGeometryCollectionXYZ;
            }
            else
            {
                if (poGeometry->IsMeasured())
                    return /*(bUseComprGeom) ?
                              OGRSpliteComprGeometryCollectionXYM :*/
                        OGRSpliteGeometryCollectionXYM;
                else
                    return /*(bUseComprGeom) ?
                              OGRSpliteComprGeometryCollectionXY :*/
                        OGRSpliteGeometryCollectionXY;
            }
            break;

        default:
            CPLError(CE_Failure, CPLE_AppDefined, "Unexpected geometry type");
            return 0;
    }
}

/************************************************************************/
/*                    ExportSpatiaLiteGeometryInternal()                */
/************************************************************************/

int OGRSQLiteLayer::ExportSpatiaLiteGeometryInternal(
    const OGRGeometry *poGeometry, OGRwkbByteOrder eByteOrder,
    bool bSpatialite2D, bool bUseComprGeom, GByte *pabyData)
{
    const auto eFGeomType = wkbFlatten(poGeometry->getGeometryType());
    switch (eFGeomType)
    {
        case wkbPoint:
        {
            const OGRPoint *poPoint = poGeometry->toPoint();
            double x = poPoint->getX();
            double y = poPoint->getY();
            memcpy(pabyData, &x, 8);
            memcpy(pabyData + 8, &y, 8);
            if (NEED_SWAP_SPATIALITE())
            {
                CPL_SWAP64PTR(pabyData);
                CPL_SWAP64PTR(pabyData + 8);
            }
            if (bSpatialite2D == true)
                return 16;
            else if (poGeometry->Is3D())
            {
                double z = poPoint->getZ();
                memcpy(pabyData + 16, &z, 8);
                if (NEED_SWAP_SPATIALITE())
                    CPL_SWAP64PTR(pabyData + 16);
                if (poGeometry->IsMeasured())
                {
                    double m = poPoint->getM();
                    memcpy(pabyData + 24, &m, 8);
                    if (NEED_SWAP_SPATIALITE())
                        CPL_SWAP64PTR(pabyData + 24);
                    return 32;
                }
                else
                    return 24;
            }
            else
            {
                if (poGeometry->IsMeasured())
                {
                    double m = poPoint->getM();
                    memcpy(pabyData + 16, &m, 8);
                    if (NEED_SWAP_SPATIALITE())
                        CPL_SWAP64PTR(pabyData + 16);
                    return 24;
                }
                else
                    return 16;
            }
        }

        case wkbLineString:
        case wkbLinearRing:
        {
            const OGRLineString *poLineString = poGeometry->toLineString();
            int nTotalSize = 4;
            int nPointCount = poLineString->getNumPoints();
            memcpy(pabyData, &nPointCount, 4);
            if (NEED_SWAP_SPATIALITE())
                CPL_SWAP32PTR(pabyData);

            if (!bUseComprGeom && !NEED_SWAP_SPATIALITE() &&
                poGeometry->CoordinateDimension() == 2)
            {
                poLineString->getPoints(
                    reinterpret_cast<OGRRawPoint *>(pabyData + 4), nullptr);
                nTotalSize += nPointCount * 16;
                return nTotalSize;
            }

            for (int i = 0; i < nPointCount; i++)
            {
                double x = poLineString->getX(i);
                double y = poLineString->getY(i);

                if (!bUseComprGeom || i == 0 || i == nPointCount - 1)
                {
                    memcpy(pabyData + nTotalSize, &x, 8);
                    memcpy(pabyData + nTotalSize + 8, &y, 8);
                    if (NEED_SWAP_SPATIALITE())
                    {
                        CPL_SWAP64PTR(pabyData + nTotalSize);
                        CPL_SWAP64PTR(pabyData + nTotalSize + 8);
                    }
                    if (!bSpatialite2D && poGeometry->Is3D())
                    {
                        double z = poLineString->getZ(i);
                        memcpy(pabyData + nTotalSize + 16, &z, 8);
                        if (NEED_SWAP_SPATIALITE())
                            CPL_SWAP64PTR(pabyData + nTotalSize + 16);
                        if (poGeometry->IsMeasured())
                        {
                            double m = poLineString->getM(i);
                            memcpy(pabyData + nTotalSize + 24, &m, 8);
                            if (NEED_SWAP_SPATIALITE())
                                CPL_SWAP64PTR(pabyData + nTotalSize + 24);
                            nTotalSize += 32;
                        }
                        else
                            nTotalSize += 24;
                    }
                    else
                    {
                        if (poGeometry->IsMeasured())
                        {
                            double m = poLineString->getM(i);
                            memcpy(pabyData + nTotalSize + 16, &m, 8);
                            if (NEED_SWAP_SPATIALITE())
                                CPL_SWAP64PTR(pabyData + nTotalSize + 16);
                            nTotalSize += 24;
                        }
                        else
                            nTotalSize += 16;
                    }
                }
                else /* Compressed intermediate points */
                {
                    float deltax =
                        static_cast<float>(x - poLineString->getX(i - 1));
                    float deltay =
                        static_cast<float>(y - poLineString->getY(i - 1));
                    memcpy(pabyData + nTotalSize, &deltax, 4);
                    memcpy(pabyData + nTotalSize + 4, &deltay, 4);
                    if (NEED_SWAP_SPATIALITE())
                    {
                        CPL_SWAP32PTR(pabyData + nTotalSize);
                        CPL_SWAP32PTR(pabyData + nTotalSize + 4);
                    }
                    if (poGeometry->Is3D())
                    {
                        double z = poLineString->getZ(i);
                        float deltaz =
                            static_cast<float>(z - poLineString->getZ(i - 1));
                        memcpy(pabyData + nTotalSize + 8, &deltaz, 4);
                        if (NEED_SWAP_SPATIALITE())
                            CPL_SWAP32PTR(pabyData + nTotalSize + 8);
                        if (poGeometry->IsMeasured())
                        {
                            double m = poLineString->getM(i);
                            memcpy(pabyData + nTotalSize + 12, &m, 8);
                            if (NEED_SWAP_SPATIALITE())
                                CPL_SWAP64PTR(pabyData + nTotalSize + 12);
                            nTotalSize += 20;
                        }
                        else
                            nTotalSize += 12;
                    }
                    else
                    {
                        if (poGeometry->IsMeasured())
                        {
                            double m = poLineString->getM(i);
                            memcpy(pabyData + nTotalSize + 8, &m, 8);
                            if (NEED_SWAP_SPATIALITE())
                                CPL_SWAP64PTR(pabyData + nTotalSize + 8);
                            nTotalSize += 16;
                        }
                        else
                            nTotalSize += 8;
                    }
                }
            }
            return nTotalSize;
        }

        case wkbPolygon:
        {
            const OGRPolygon *poPoly = poGeometry->toPolygon();
            int nTotalSize = 4;
            if (poPoly->getExteriorRing() != nullptr)
            {
                int nInteriorRingCount = poPoly->getNumInteriorRings();
                const int nParts = 1 + nInteriorRingCount;
                memcpy(pabyData, &nParts, 4);
                if (NEED_SWAP_SPATIALITE())
                    CPL_SWAP32PTR(pabyData);

                nTotalSize += ExportSpatiaLiteGeometryInternal(
                    poPoly->getExteriorRing(), eByteOrder, bSpatialite2D,
                    bUseComprGeom, pabyData + nTotalSize);

                for (int i = 0; i < nInteriorRingCount; i++)
                {
                    nTotalSize += ExportSpatiaLiteGeometryInternal(
                        poPoly->getInteriorRing(i), eByteOrder, bSpatialite2D,
                        bUseComprGeom, pabyData + nTotalSize);
                }
            }
            else
            {
                memset(pabyData, 0, 4);
            }
            return nTotalSize;
        }

        case wkbMultiPoint:
        case wkbMultiLineString:
        case wkbMultiPolygon:
        case wkbGeometryCollection:
        {
            const OGRGeometryCollection *poGeomCollection =
                poGeometry->toGeometryCollection();
            int nTotalSize = 4;

            std::vector<const OGRGeometry *> simpleGeometries;
            collectSimpleGeometries(poGeomCollection, simpleGeometries);

            int nParts = static_cast<int>(simpleGeometries.size());
            memcpy(pabyData, &nParts, 4);
            if (NEED_SWAP_SPATIALITE())
                CPL_SWAP32PTR(pabyData);

            for (int i = 0; i < nParts; i++)
            {
                pabyData[nTotalSize] = 0x69;
                nTotalSize++;

                const OGRGeometry *poPart = simpleGeometries[i];
                int nCode = GetSpatialiteGeometryCode(poPart, bSpatialite2D,
                                                      bUseComprGeom, FALSE);
                if (nCode == 0)
                    return 0;
                memcpy(pabyData + nTotalSize, &nCode, 4);
                if (NEED_SWAP_SPATIALITE())
                    CPL_SWAP32PTR(pabyData + nTotalSize);
                nTotalSize += 4;
                nTotalSize += ExportSpatiaLiteGeometryInternal(
                    poPart, eByteOrder, bSpatialite2D, bUseComprGeom,
                    pabyData + nTotalSize);
            }
            return nTotalSize;
        }

        default:
            return 0;
    }
}

OGRErr OGRSQLiteLayer::ExportSpatiaLiteGeometry(
    const OGRGeometry *poGeometry, GInt32 nSRID, OGRwkbByteOrder eByteOrder,
    bool bSpatialite2D, bool bUseComprGeom, GByte **ppabyData,
    int *pnDataLength)

{
    /* Spatialite does not support curve geometries */
    const OGRGeometry *poWorkGeom = poGeometry->hasCurveGeometry()
                                        ? poGeometry->getLinearGeometry()
                                        : poGeometry;

    bUseComprGeom = bUseComprGeom && !bSpatialite2D &&
                    CanBeCompressedSpatialiteGeometry(poWorkGeom);

    const int nGeomSize =
        ComputeSpatiaLiteGeometrySize(poWorkGeom, bSpatialite2D, bUseComprGeom);
    if (nGeomSize == 0)
    {
        *ppabyData = nullptr;
        *pnDataLength = 0;
        return OGRERR_FAILURE;
    }
    const int nDataLen = 44 + nGeomSize;
    OGREnvelope sEnvelope;

    *ppabyData = static_cast<GByte *>(CPLMalloc(nDataLen));

    (*ppabyData)[0] = 0x00;
    (*ppabyData)[1] = static_cast<GByte>(eByteOrder);

    // Write out SRID
    memcpy(*ppabyData + 2, &nSRID, 4);

    // Write out the geometry bounding rectangle
    poGeometry->getEnvelope(&sEnvelope);
    memcpy(*ppabyData + 6, &sEnvelope.MinX, 8);
    memcpy(*ppabyData + 14, &sEnvelope.MinY, 8);
    memcpy(*ppabyData + 22, &sEnvelope.MaxX, 8);
    memcpy(*ppabyData + 30, &sEnvelope.MaxY, 8);

    (*ppabyData)[38] = 0x7C;

    int nCode = GetSpatialiteGeometryCode(poWorkGeom, bSpatialite2D,
                                          bUseComprGeom, TRUE);
    if (nCode == 0)
    {
        CPLFree(*ppabyData);
        *ppabyData = nullptr;
        *pnDataLength = 0;
        if (poWorkGeom != poGeometry)
            delete poWorkGeom;
        return OGRERR_FAILURE;
    }
    memcpy(*ppabyData + 39, &nCode, 4);

    int nWritten = ExportSpatiaLiteGeometryInternal(
        poWorkGeom, eByteOrder, bSpatialite2D, bUseComprGeom, *ppabyData + 43);
    if (poWorkGeom != poGeometry)
        delete poWorkGeom;

    if (nWritten == 0)
    {
        CPLFree(*ppabyData);
        *ppabyData = nullptr;
        *pnDataLength = 0;
        return OGRERR_FAILURE;
    }

    (*ppabyData)[nDataLen - 1] = 0xFE;

    if (NEED_SWAP_SPATIALITE())
    {
        CPL_SWAP32PTR(*ppabyData + 2);
        CPL_SWAP64PTR(*ppabyData + 6);
        CPL_SWAP64PTR(*ppabyData + 14);
        CPL_SWAP64PTR(*ppabyData + 22);
        CPL_SWAP64PTR(*ppabyData + 30);
        CPL_SWAP32PTR(*ppabyData + 39);
    }

    *pnDataLength = nDataLen;

    return OGRERR_NONE;
}

/************************************************************************/
/*                           TestCapability()                           */
/************************************************************************/

int OGRSQLiteLayer::TestCapability(const char *pszCap)

{
    if (EQUAL(pszCap, OLCRandomRead))
        return FALSE;

    else if (EQUAL(pszCap, OLCFastFeatureCount))
        return FALSE;

    else if (EQUAL(pszCap, OLCFastSpatialFilter))
        return FALSE;

    else if (EQUAL(pszCap, OLCIgnoreFields))
        return TRUE;

    else if (EQUAL(pszCap, OLCTransactions))
        return TRUE;

    else
        return FALSE;
}

/************************************************************************/
/*                          StartTransaction()                          */
/************************************************************************/

OGRErr OGRSQLiteLayer::StartTransaction()
{
    return m_poDS->StartTransaction();
}

/************************************************************************/
/*                         CommitTransaction()                          */
/************************************************************************/

OGRErr OGRSQLiteLayer::CommitTransaction()
{
    return m_poDS->CommitTransaction();
}

/************************************************************************/
/*                        RollbackTransaction()                         */
/************************************************************************/

OGRErr OGRSQLiteLayer::RollbackTransaction()
{
    return m_poDS->RollbackTransaction();
}

/************************************************************************/
/*                           ClearStatement()                           */
/************************************************************************/

void OGRSQLiteLayer::ClearStatement()

{
    if (m_hStmt != nullptr)
    {
#ifdef DEBUG_VERBOSE
        CPLDebug("OGR_SQLITE", "finalize %p", m_hStmt);
#endif
        sqlite3_finalize(m_hStmt);
        m_hStmt = nullptr;
    }
}

/************************************************************************/
/*                     FormatSpatialFilterFromRTree()                   */
/************************************************************************/

CPLString OGRSQLiteLayer::FormatSpatialFilterFromRTree(
    OGRGeometry *poFilterGeom, const char *pszRowIDName,
    const char *pszEscapedTable, const char *pszEscapedGeomCol)
{
    CPLString osSpatialWHERE;
    OGREnvelope sEnvelope;

    poFilterGeom->getEnvelope(&sEnvelope);

    if (std::isinf(sEnvelope.MinX) && sEnvelope.MinX < 0 &&
        std::isinf(sEnvelope.MinY) && sEnvelope.MinY < 0 &&
        std::isinf(sEnvelope.MaxX) && sEnvelope.MaxX > 0 &&
        std::isinf(sEnvelope.MaxY) && sEnvelope.MaxY > 0)
        return "";

    osSpatialWHERE.Printf(
        "%s IN ( SELECT pkid FROM 'idx_%s_%s' WHERE "
        "xmax >= %.12f AND xmin <= %.12f AND ymax >= %.12f AND ymin <= %.12f)",
        pszRowIDName, pszEscapedTable, pszEscapedGeomCol,
        sEnvelope.MinX - 1e-11, sEnvelope.MaxX + 1e-11, sEnvelope.MinY - 1e-11,
        sEnvelope.MaxY + 1e-11);

    return osSpatialWHERE;
}

/************************************************************************/
/*                     FormatSpatialFilterFromMBR()                     */
/************************************************************************/

CPLString
OGRSQLiteLayer::FormatSpatialFilterFromMBR(OGRGeometry *poFilterGeom,
                                           const char *pszEscapedGeomColName)
{
    CPLString osSpatialWHERE;
    OGREnvelope sEnvelope;

    poFilterGeom->getEnvelope(&sEnvelope);

    if (std::isinf(sEnvelope.MinX) && sEnvelope.MinX < 0 &&
        std::isinf(sEnvelope.MinY) && sEnvelope.MinY < 0 &&
        std::isinf(sEnvelope.MaxX) && sEnvelope.MaxX > 0 &&
        std::isinf(sEnvelope.MaxY) && sEnvelope.MaxY > 0)
        return "";

    /* A bit inefficient but still faster than OGR filtering */
    osSpatialWHERE.Printf(
        "MBRIntersects(\"%s\", BuildMBR(%.12f, %.12f, %.12f, %.12f))",
        pszEscapedGeomColName,
        // Insure that only Decimal.Points are used, never local settings such
        // as Decimal.Comma.
        sEnvelope.MinX - 1e-11, sEnvelope.MinY - 1e-11, sEnvelope.MaxX + 1e-11,
        sEnvelope.MaxY + 1e-11);

    return osSpatialWHERE;
}

/************************************************************************/
/*                 OGRSQLiteGetSpatialiteGeometryHeader()               */
/************************************************************************/

OGRErr OGRSQLiteGetSpatialiteGeometryHeader(const GByte *pabyData, int nBytes,
                                            int *pnSRID,
                                            OGRwkbGeometryType *peType,
                                            bool *pbIsEmpty, double *pdfMinX,
                                            double *pdfMinY, double *pdfMaxX,
                                            double *pdfMaxY)
{
    return OGRSQLiteLayer::GetSpatialiteGeometryHeader(
        pabyData, nBytes, pnSRID, peType, pbIsEmpty, pdfMinX, pdfMinY, pdfMaxX,
        pdfMaxY);
}

/************************************************************************/
/*                   OGRSQLiteImportSpatiaLiteGeometry()                */
/************************************************************************/

OGRErr OGRSQLiteImportSpatiaLiteGeometry(const GByte *pabyData, int nBytes,
                                         OGRGeometry **ppoGeometry, int *pnSRID)
{
    return OGRSQLiteLayer::ImportSpatiaLiteGeometry(pabyData, nBytes,
                                                    ppoGeometry, pnSRID);
}

/************************************************************************/
/*                   OGRSQLiteExportSpatiaLiteGeometry()                */
/************************************************************************/

OGRErr OGRSQLiteExportSpatiaLiteGeometry(const OGRGeometry *poGeometry,
                                         GInt32 nSRID,
                                         OGRwkbByteOrder eByteOrder,
                                         bool bSpatialite2D, bool bUseComprGeom,
                                         GByte **ppabyData, int *pnDataLength)
{
    return OGRSQLiteLayer::ExportSpatiaLiteGeometry(
        poGeometry, nSRID, eByteOrder, bSpatialite2D, bUseComprGeom, ppabyData,
        pnDataLength);
}

/************************************************************************/
/*                             GetDataset()                             */
/************************************************************************/

GDALDataset *OGRSQLiteLayer::GetDataset()
{
    return m_poDS;
}
