LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/arrow_common - ograrrowlayer.hpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2791 3171 88.0 %
Date: 2026-04-19 18:43:50 Functions: 137 154 89.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Arrow generic code
       4             :  * Purpose:  Arrow generic code
       5             :  * Author:   Even Rouault, <even.rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2022, Planet Labs
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #ifndef OGARROWLAYER_HPP_INCLUDED
      14             : #define OGARROWLAYER_HPP_INCLUDED
      15             : 
      16             : #include "ogr_arrow.h"
      17             : 
      18             : #include "cpl_float.h"
      19             : #include "cpl_json.h"
      20             : #include "cpl_time.h"
      21             : #include "ogrlayerarrow.h"
      22             : #include "ogr_p.h"
      23             : #include "ogr_swq.h"
      24             : #include "ogr_wkb.h"
      25             : 
      26             : #include <algorithm>
      27             : #include <cassert>
      28             : #include <cinttypes>
      29             : #include <limits>
      30             : #include <string_view>
      31             : 
      32             : #define SWQ_ISNOTNULL (-SWQ_ISNULL)
      33             : 
      34             : #if defined(__clang__)
      35             : #pragma clang diagnostic push
      36             : #pragma clang diagnostic ignored "-Wweak-vtables"
      37             : #endif
      38             : 
      39             : inline IOGRArrowLayer::~IOGRArrowLayer() = default;
      40             : 
      41             : /************************************************************************/
      42             : /*                           OGRArrowLayer()                            */
      43             : /************************************************************************/
      44             : 
      45        1966 : inline OGRArrowLayer::OGRArrowLayer(OGRArrowDataset *poDS,
      46             :                                     const char *pszLayerName,
      47        1966 :                                     bool bListsAsStringJson)
      48             :     : m_poArrowDS(poDS), m_bListsAsStringJson(bListsAsStringJson),
      49        1966 :       m_poMemoryPool(poDS->GetMemoryPool())
      50             : {
      51        1966 :     m_poFeatureDefn = new OGRFeatureDefn(pszLayerName);
      52        1966 :     m_poFeatureDefn->SetGeomType(wkbNone);
      53        1966 :     m_poFeatureDefn->Reference();
      54        1966 :     SetDescription(pszLayerName);
      55        1966 : }
      56             : 
      57             : /************************************************************************/
      58             : /*                          ~OGRFeatherLayer()                          */
      59             : /************************************************************************/
      60             : 
      61        1963 : inline OGRArrowLayer::~OGRArrowLayer()
      62             : {
      63        1963 :     if (m_sCachedSchema.release)
      64          59 :         m_sCachedSchema.release(&m_sCachedSchema);
      65             : 
      66        1963 :     CPLDebug("ARROW", "Memory pool: bytes_allocated = %" PRId64,
      67        1963 :              m_poMemoryPool->bytes_allocated());
      68        1963 :     CPLDebug("ARROW", "Memory pool: max_memory = %" PRId64,
      69        1963 :              m_poMemoryPool->max_memory());
      70        1963 :     m_poFeatureDefn->Release();
      71        1963 : }
      72             : 
      73             : /************************************************************************/
      74             : /*                           LoadGDALSchema()                           */
      75             : /************************************************************************/
      76             : 
      77             : inline std::map<std::string, std::unique_ptr<OGRFieldDefn>>
      78        1966 : OGRArrowLayer::LoadGDALSchema(const arrow::KeyValueMetadata *kv_metadata)
      79             : {
      80             :     std::map<std::string, std::unique_ptr<OGRFieldDefn>>
      81        1966 :         oMapFieldNameToGDALSchemaFieldDefn;
      82        2009 :     if (kv_metadata && kv_metadata->Contains("gdal:schema") &&
      83          43 :         CPLTestBool(CPLGetConfigOption(
      84        2009 :             ("OGR_" + GetDriverUCName() + "_READ_GDAL_SCHEMA").c_str(), "YES")))
      85             :     {
      86          86 :         auto gdalSchema = kv_metadata->Get("gdal:schema");
      87          43 :         if (gdalSchema.ok())
      88             :         {
      89          43 :             CPLDebug(GetDriverUCName().c_str(), "gdal:schema = %s",
      90             :                      gdalSchema->c_str());
      91          86 :             CPLJSONDocument oDoc;
      92          43 :             if (oDoc.LoadMemory(*gdalSchema))
      93             :             {
      94          86 :                 auto oRoot = oDoc.GetRoot();
      95             : 
      96          43 :                 m_osFIDColumn = oRoot.GetString("fid");
      97             : 
      98         129 :                 auto oColumns = oRoot.GetObj("columns");
      99          43 :                 if (oColumns.IsValid())
     100             :                 {
     101         432 :                     for (const auto &oColumn : oColumns.GetChildren())
     102             :                     {
     103         778 :                         const auto osName = oColumn.GetName();
     104        1167 :                         const auto osType = oColumn.GetString("type");
     105        1167 :                         const auto osSubType = oColumn.GetString("subtype");
     106             :                         auto poFieldDefn = std::make_unique<OGRFieldDefn>(
     107         778 :                             osName.c_str(), OFTString);
     108        2327 :                         for (int iType = 0;
     109        2327 :                              iType <= static_cast<int>(OFTMaxType); iType++)
     110             :                         {
     111        2327 :                             if (EQUAL(osType.c_str(),
     112             :                                       OGRFieldDefn::GetFieldTypeName(
     113             :                                           static_cast<OGRFieldType>(iType))))
     114             :                             {
     115         389 :                                 poFieldDefn->SetType(
     116             :                                     static_cast<OGRFieldType>(iType));
     117         389 :                                 break;
     118             :                             }
     119             :                         }
     120         389 :                         if (!osSubType.empty())
     121             :                         {
     122         500 :                             for (int iSubType = 0;
     123         500 :                                  iSubType <= static_cast<int>(OFSTMaxSubType);
     124             :                                  iSubType++)
     125             :                             {
     126         500 :                                 if (EQUAL(osSubType.c_str(),
     127             :                                           OGRFieldDefn::GetFieldSubTypeName(
     128             :                                               static_cast<OGRFieldSubType>(
     129             :                                                   iSubType))))
     130             :                                 {
     131         112 :                                     poFieldDefn->SetSubType(
     132             :                                         static_cast<OGRFieldSubType>(iSubType));
     133         112 :                                     break;
     134             :                                 }
     135             :                             }
     136             :                         }
     137         389 :                         poFieldDefn->SetWidth(oColumn.GetInteger("width"));
     138         389 :                         poFieldDefn->SetPrecision(
     139             :                             oColumn.GetInteger("precision"));
     140             : 
     141             :                         const auto osAlternativeName =
     142        1167 :                             oColumn.GetString("alternative_name");
     143         389 :                         if (!osAlternativeName.empty())
     144           2 :                             poFieldDefn->SetAlternativeName(
     145             :                                 osAlternativeName.c_str());
     146             : 
     147        1167 :                         const auto osComment = oColumn.GetString("comment");
     148         389 :                         if (!osComment.empty())
     149           3 :                             poFieldDefn->SetComment(osComment);
     150             : 
     151         389 :                         oMapFieldNameToGDALSchemaFieldDefn[osName] =
     152         778 :                             std::move(poFieldDefn);
     153             :                     }
     154             :                 }
     155             :             }
     156             :         }
     157             :     }
     158        1966 :     return oMapFieldNameToGDALSchemaFieldDefn;
     159             : }
     160             : 
     161             : /************************************************************************/
     162             : /*                          LoadGDALMetadata()                          */
     163             : /************************************************************************/
     164             : 
     165             : inline void
     166        1418 : OGRArrowLayer::LoadGDALMetadata(const arrow::KeyValueMetadata *kv_metadata)
     167             : {
     168        1418 :     if (kv_metadata && kv_metadata->Contains("gdal:metadata"))
     169             :     {
     170          22 :         auto gdalMetadata = kv_metadata->Get("gdal:metadata");
     171          11 :         if (gdalMetadata.ok())
     172             :         {
     173          22 :             CPLJSONDocument oDoc;
     174          11 :             if (oDoc.LoadMemory(*gdalMetadata))
     175             :             {
     176          22 :                 auto oRoot = oDoc.GetRoot();
     177          24 :                 for (const auto &oDomain : oRoot.GetChildren())
     178             :                 {
     179          14 :                     if (STARTS_WITH(oDomain.GetName().c_str(), "json:") &&
     180           1 :                         oDomain.GetType() == CPLJSONObject::Type::Object)
     181             :                     {
     182           1 :                         char **papszMD = nullptr;
     183           1 :                         papszMD = CSLAddString(
     184             :                             papszMD,
     185           2 :                             oDomain.Format(CPLJSONObject::PrettyFormat::Plain)
     186             :                                 .c_str());
     187           1 :                         SetMetadata(papszMD, oDomain.GetName().c_str());
     188           1 :                         CSLDestroy(papszMD);
     189             :                     }
     190          13 :                     else if (STARTS_WITH(oDomain.GetName().c_str(), "xml:") &&
     191           1 :                              oDomain.GetType() == CPLJSONObject::Type::String)
     192             :                     {
     193           1 :                         char **papszMD = nullptr;
     194             :                         papszMD =
     195           1 :                             CSLAddString(papszMD, oDomain.ToString().c_str());
     196           1 :                         SetMetadata(papszMD, oDomain.GetName().c_str());
     197           1 :                         CSLDestroy(papszMD);
     198             :                     }
     199             :                     else
     200             :                     {
     201          22 :                         for (const auto &oItem : oDomain.GetChildren())
     202             :                         {
     203          11 :                             if (oItem.GetType() == CPLJSONObject::Type::String)
     204             :                             {
     205          22 :                                 SetMetadataItem(oItem.GetName().c_str(),
     206          22 :                                                 oItem.ToString().c_str(),
     207          22 :                                                 oDomain.GetName().c_str());
     208             :                             }
     209             :                         }
     210             :                     }
     211             :                 }
     212             :             }
     213             :         }
     214             :     }
     215        1418 : }
     216             : 
     217             : /************************************************************************/
     218             : /*                         IsIntegerArrowType()                         */
     219             : /************************************************************************/
     220             : 
     221        9033 : inline bool OGRArrowLayer::IsIntegerArrowType(arrow::Type::type typeId)
     222             : {
     223        8630 :     return typeId == arrow::Type::INT8 || typeId == arrow::Type::UINT8 ||
     224        7824 :            typeId == arrow::Type::INT16 || typeId == arrow::Type::UINT16 ||
     225        6606 :            typeId == arrow::Type::INT32 || typeId == arrow::Type::UINT32 ||
     226       17663 :            typeId == arrow::Type::INT64 || typeId == arrow::Type::UINT64;
     227             : }
     228             : 
     229             : /************************************************************************/
     230             : /*                       IsHandledListOrMapType()                       */
     231             : /************************************************************************/
     232             : 
     233        9024 : inline bool OGRArrowLayer::IsHandledListOrMapType(
     234             :     const std::shared_ptr<arrow::DataType> &valueType)
     235             : {
     236        9024 :     const auto itemTypeId = valueType->id();
     237        8621 :     return itemTypeId == arrow::Type::BOOL || IsIntegerArrowType(itemTypeId) ||
     238        5349 :            itemTypeId == arrow::Type::HALF_FLOAT ||
     239        4946 :            itemTypeId == arrow::Type::FLOAT ||
     240        4543 :            itemTypeId == arrow::Type::DOUBLE ||
     241             : #if ARROW_VERSION_MAJOR >= 18
     242        4543 :            itemTypeId == arrow::Type::DECIMAL32 ||
     243        4543 :            itemTypeId == arrow::Type::DECIMAL64 ||
     244             : #endif
     245        3766 :            itemTypeId == arrow::Type::DECIMAL128 ||
     246        3741 :            itemTypeId == arrow::Type::DECIMAL256 ||
     247        1758 :            itemTypeId == arrow::Type::STRING ||
     248        1733 :            itemTypeId == arrow::Type::LARGE_STRING ||
     249             : #if ARROW_VERSION_MAJOR >= 15
     250        1729 :            itemTypeId == arrow::Type::STRING_VIEW ||
     251             : #endif
     252        1728 :            itemTypeId == arrow::Type::BINARY ||
     253             : #if ARROW_VERSION_MAJOR >= 15
     254        1728 :            itemTypeId == arrow::Type::BINARY_VIEW ||
     255             : #endif
     256        1268 :            itemTypeId == arrow::Type::STRUCT ||
     257          26 :            (itemTypeId == arrow::Type::MAP &&
     258          13 :             IsHandledMapType(
     259       27937 :                 std::static_pointer_cast<arrow::MapType>(valueType))) ||
     260          50 :            ((itemTypeId == arrow::Type::LIST ||
     261          25 :              itemTypeId == arrow::Type::LARGE_LIST ||
     262        2510 :              itemTypeId == arrow::Type::FIXED_SIZE_LIST) &&
     263        1255 :             IsHandledListType(
     264       19303 :                 std::static_pointer_cast<arrow::BaseListType>(valueType)));
     265             : }
     266             : 
     267             : /************************************************************************/
     268             : /*                         IsHandledListType()                          */
     269             : /************************************************************************/
     270             : 
     271        1760 : inline bool OGRArrowLayer::IsHandledListType(
     272             :     const std::shared_ptr<arrow::BaseListType> &listType)
     273             : {
     274        1760 :     return IsHandledListOrMapType(listType->value_type());
     275             : }
     276             : 
     277             : /************************************************************************/
     278             : /*                          IsHandledMapType()                          */
     279             : /************************************************************************/
     280             : 
     281             : inline bool
     282        7264 : OGRArrowLayer::IsHandledMapType(const std::shared_ptr<arrow::MapType> &mapType)
     283             : {
     284        7264 :     const auto typeId = mapType->key_type()->id();
     285             :     return (typeId == arrow::Type::STRING
     286             : #if ARROW_VERSION_MAJOR >= 15
     287           2 :             || typeId == arrow::Type::STRING_VIEW
     288             : #endif
     289       14528 :             ) &&
     290       14528 :            IsHandledListOrMapType(mapType->item_type());
     291             : }
     292             : 
     293             : /************************************************************************/
     294             : /*                       GetFieldExtensionName()                        */
     295             : /************************************************************************/
     296             : 
     297             : inline static std::string
     298       37521 : GetFieldExtensionName(const std::shared_ptr<arrow::Field> &field,
     299             :                       std::shared_ptr<arrow::DataType> &type,
     300             :                       const char *pszDebugKey)
     301             : {
     302       37521 :     std::string osExtensionName;
     303       37521 :     if (type->id() == arrow::Type::EXTENSION)
     304             :     {
     305         176 :         auto extensionType = cpl::down_cast<arrow::ExtensionType *>(type.get());
     306         176 :         osExtensionName = extensionType->extension_name();
     307         176 :         type = extensionType->storage_type();
     308             :     }
     309       37357 :     else if (const auto &field_kv_metadata = field->metadata())
     310             :     {
     311          24 :         auto extension_name = field_kv_metadata->Get(ARROW_EXTENSION_NAME_KEY);
     312          12 :         if (extension_name.ok())
     313             :         {
     314          10 :             osExtensionName = *extension_name;
     315             :         }
     316             :     }
     317             : 
     318       37707 :     if (!osExtensionName.empty() &&
     319       37707 :         osExtensionName != EXTENSION_NAME_ARROW_JSON &&
     320          10 :         osExtensionName != EXTENSION_NAME_ARROW_TIMESTAMP_WITH_OFFSET)
     321             :     {
     322           6 :         CPLDebug(pszDebugKey,
     323             :                  "Dealing with field %s of extension type %s as %s",
     324           3 :                  field->name().c_str(), osExtensionName.c_str(),
     325           6 :                  type->ToString().c_str());
     326             :     }
     327       37521 :     return osExtensionName;
     328             : }
     329             : 
     330             : /************************************************************************/
     331             : /*                         MapArrowTypeToOGR()                          */
     332             : /************************************************************************/
     333             : 
     334       36638 : inline bool OGRArrowLayer::MapArrowTypeToOGR(
     335             :     const std::shared_ptr<arrow::DataType> &typeIn,
     336             :     const std::shared_ptr<arrow::Field> &field, OGRFieldDefn &oField,
     337             :     OGRFieldType &eType, OGRFieldSubType &eSubType,
     338             :     const std::vector<int> &path,
     339             :     const std::map<std::string, std::unique_ptr<OGRFieldDefn>>
     340             :         &oMapFieldNameToGDALSchemaFieldDefn)
     341             : {
     342       36638 :     bool bTypeOK = false;
     343             : 
     344       73276 :     std::shared_ptr<arrow::DataType> type(typeIn);
     345             :     const std::string osExtensionName =
     346       36638 :         GetFieldExtensionName(field, type, GetDriverUCName().c_str());
     347       36638 :     switch (type->id())
     348             :     {
     349         384 :         case arrow::Type::NA:
     350         384 :             break;
     351             : 
     352         414 :         case arrow::Type::BOOL:
     353         414 :             bTypeOK = true;
     354         414 :             eType = OFTInteger;
     355         414 :             eSubType = OFSTBoolean;
     356         414 :             break;
     357        1200 :         case arrow::Type::UINT8:
     358             :         case arrow::Type::INT8:
     359             :         case arrow::Type::UINT16:
     360        1200 :             bTypeOK = true;
     361        1200 :             eType = OFTInteger;
     362        1200 :             break;
     363         408 :         case arrow::Type::INT16:
     364         408 :             bTypeOK = true;
     365         408 :             eType = OFTInteger;
     366         408 :             eSubType = OFSTInt16;
     367         408 :             break;
     368          25 :         case arrow::Type::UINT32:
     369          25 :             bTypeOK = true;
     370          25 :             eType = OFTInteger64;
     371          25 :             break;
     372        1430 :         case arrow::Type::INT32:
     373        1430 :             bTypeOK = true;
     374        1430 :             eType = OFTInteger;
     375        1430 :             break;
     376         400 :         case arrow::Type::UINT64:
     377         400 :             bTypeOK = true;
     378         400 :             eType = OFTReal;  // potential loss
     379         400 :             break;
     380        1744 :         case arrow::Type::INT64:
     381        1744 :             bTypeOK = true;
     382        1744 :             eType = OFTInteger64;
     383        1744 :             break;
     384         526 :         case arrow::Type::HALF_FLOAT:  // should use OFSTFloat16 if we had it
     385             :         case arrow::Type::FLOAT:
     386         526 :             bTypeOK = true;
     387         526 :             eType = OFTReal;
     388         526 :             eSubType = OFSTFloat32;
     389         526 :             break;
     390         901 :         case arrow::Type::DOUBLE:
     391         901 :             bTypeOK = true;
     392         901 :             eType = OFTReal;
     393         901 :             break;
     394        2848 :         case arrow::Type::STRING:
     395             :         case arrow::Type::LARGE_STRING:
     396             : #if ARROW_VERSION_MAJOR >= 15
     397             :         case arrow::Type::STRING_VIEW:
     398             : #endif
     399        2848 :             bTypeOK = true;
     400        2848 :             eType = OFTString;
     401        2848 :             if (osExtensionName == EXTENSION_NAME_ARROW_JSON)
     402         176 :                 eSubType = OFSTJSON;
     403        2848 :             break;
     404         841 :         case arrow::Type::BINARY:
     405             :         case arrow::Type::LARGE_BINARY:
     406             : #if ARROW_VERSION_MAJOR >= 15
     407             :         case arrow::Type::BINARY_VIEW:
     408             : #endif
     409         841 :             bTypeOK = true;
     410         841 :             eType = OFTBinary;
     411         841 :             break;
     412         408 :         case arrow::Type::FIXED_SIZE_BINARY:
     413         408 :             bTypeOK = true;
     414         408 :             eType = OFTBinary;
     415         408 :             oField.SetWidth(
     416         816 :                 std::static_pointer_cast<arrow::FixedSizeBinaryType>(type)
     417         408 :                     ->byte_width());
     418         408 :             break;
     419             : 
     420         861 :         case arrow::Type::DATE32:
     421             :         case arrow::Type::DATE64:
     422         861 :             bTypeOK = true;
     423         861 :             eType = OFTDate;
     424         861 :             break;
     425             : 
     426        2498 :         case arrow::Type::TIMESTAMP:
     427             :         {
     428        2498 :             bTypeOK = true;
     429             :             const auto timestampType =
     430        2498 :                 static_cast<arrow::TimestampType *>(type.get());
     431        2498 :             eType = OFTDateTime;
     432        2498 :             const auto &osTZ = timestampType->timezone();
     433        2498 :             int nTZFlag = OGRTimezoneToTZFlag(osTZ.c_str(), false);
     434        2498 :             if (nTZFlag == OGR_TZFLAG_UNKNOWN && !osTZ.empty())
     435             :             {
     436           0 :                 CPLDebug(GetDriverUCName().c_str(),
     437             :                          "Field %s has unrecognized timezone %s. "
     438             :                          "UTC datetime will be used instead.",
     439           0 :                          field->name().c_str(), osTZ.c_str());
     440           0 :                 nTZFlag = OGR_TZFLAG_UTC;
     441             :             }
     442        2498 :             oField.SetTZFlag(nTZFlag);
     443        2498 :             break;
     444             :         }
     445             : 
     446         816 :         case arrow::Type::TIME32:
     447         816 :             bTypeOK = true;
     448         816 :             eType = OFTTime;
     449         816 :             break;
     450             : 
     451         800 :         case arrow::Type::TIME64:
     452         800 :             bTypeOK = true;
     453         800 :             eType = OFTInteger64;  // our OFTTime doesn't have micro or
     454             :                                    // nanosecond accuracy
     455         800 :             break;
     456             : 
     457             : #if ARROW_VERSION_MAJOR >= 18
     458         828 :         case arrow::Type::DECIMAL32:
     459             :         case arrow::Type::DECIMAL64:
     460             : #endif
     461             :         case arrow::Type::DECIMAL128:
     462             :         case arrow::Type::DECIMAL256:
     463             :         {
     464         828 :             bTypeOK = true;
     465             :             const auto decimalType =
     466        1656 :                 std::static_pointer_cast<arrow::DecimalType>(type);
     467         828 :             eType = OFTReal;
     468         828 :             oField.SetWidth(decimalType->precision());
     469         828 :             oField.SetPrecision(decimalType->scale());
     470         828 :             break;
     471             :         }
     472             : 
     473       12055 :         case arrow::Type::LIST:
     474             :         case arrow::Type::LARGE_LIST:
     475             :         case arrow::Type::FIXED_SIZE_LIST:
     476             :         {
     477       12055 :             bTypeOK = true;
     478       24110 :             auto listType = std::static_pointer_cast<arrow::BaseListType>(type);
     479       12055 :             switch (listType->value_type()->id())
     480             :             {
     481         816 :                 case arrow::Type::BOOL:
     482         816 :                     eType = OFTIntegerList;
     483         816 :                     eSubType = OFSTBoolean;
     484         816 :                     break;
     485        4089 :                 case arrow::Type::UINT8:
     486             :                 case arrow::Type::INT8:
     487             :                 case arrow::Type::UINT16:
     488             :                 case arrow::Type::INT16:
     489             :                 case arrow::Type::INT32:
     490        4089 :                     eType = OFTIntegerList;
     491        4089 :                     break;
     492          48 :                 case arrow::Type::UINT32:
     493          48 :                     eType = OFTInteger64List;
     494          48 :                     break;
     495         800 :                 case arrow::Type::UINT64:
     496         800 :                     eType = OFTRealList;  // potential loss
     497         800 :                     break;
     498        1996 :                 case arrow::Type::INT64:
     499        1996 :                     eType = OFTInteger64List;
     500        1996 :                     break;
     501         846 :                 case arrow::Type::HALF_FLOAT:  // should use OFSTFloat16 if we
     502             :                                                // had it
     503             :                 case arrow::Type::FLOAT:
     504         846 :                     eType = OFTRealList;
     505         846 :                     eSubType = OFSTFloat32;
     506         846 :                     break;
     507        1670 :                 case arrow::Type::DOUBLE:
     508             : #if ARROW_VERSION_MAJOR >= 18
     509             :                 case arrow::Type::DECIMAL32:
     510             :                 case arrow::Type::DECIMAL64:
     511             : #endif
     512             :                 case arrow::Type::DECIMAL128:
     513             :                 case arrow::Type::DECIMAL256:
     514        1670 :                     eType = OFTRealList;
     515        1670 :                     break;
     516        1285 :                 case arrow::Type::STRING:
     517             :                 case arrow::Type::LARGE_STRING:
     518             :                 case arrow::Type::BINARY:
     519             : #if ARROW_VERSION_MAJOR >= 15
     520             :                 case arrow::Type::STRING_VIEW:
     521             : #endif
     522        1285 :                     eType = OFTStringList;
     523        1285 :                     break;
     524         505 :                 default:
     525             :                 {
     526         505 :                     if (IsHandledListType(listType))
     527             :                     {
     528         505 :                         eType = OFTString;
     529         505 :                         eSubType = OFSTJSON;
     530             :                     }
     531             :                     else
     532             :                     {
     533           0 :                         bTypeOK = false;
     534           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     535             :                                  "Field %s of unhandled type %s ignored",
     536           0 :                                  field->name().c_str(),
     537           0 :                                  type->ToString().c_str());
     538             :                     }
     539         505 :                     break;
     540             :                 }
     541             :             }
     542             : 
     543       12055 :             if (bTypeOK && m_bListsAsStringJson)
     544             :             {
     545          61 :                 eType = OFTString;
     546          61 :                 eSubType = OFSTJSON;
     547             :             }
     548             : 
     549       12055 :             break;
     550             :         }
     551             : 
     552        7251 :         case arrow::Type::MAP:
     553             :         {
     554        7251 :             bTypeOK = true;
     555       14502 :             auto mapType = std::static_pointer_cast<arrow::MapType>(type);
     556        7251 :             if (IsHandledMapType(mapType))
     557             :             {
     558        7251 :                 eType = OFTString;
     559        7251 :                 eSubType = OFSTJSON;
     560             :             }
     561             :             else
     562             :             {
     563           0 :                 bTypeOK = false;
     564           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     565             :                          "Field %s of unhandled type %s ignored",
     566           0 :                          field->name().c_str(), type->ToString().c_str());
     567             :             }
     568        7251 :             break;
     569             :         }
     570             : 
     571           0 :         case arrow::Type::STRUCT:
     572             :             // should be handled by specialized code
     573           0 :             CPLAssert(false);
     574             :             break;
     575             : 
     576             :             // unhandled types
     577             : 
     578           0 :         case arrow::Type::INTERVAL_MONTHS:
     579             :         case arrow::Type::INTERVAL_DAY_TIME:
     580             :         case arrow::Type::SPARSE_UNION:
     581             :         case arrow::Type::DENSE_UNION:
     582             :         case arrow::Type::DICTIONARY:
     583             :         case arrow::Type::EXTENSION:
     584             :         case arrow::Type::DURATION:
     585             :         case arrow::Type::INTERVAL_MONTH_DAY_NANO:
     586             : #if ARROW_VERSION_MAJOR >= 12
     587             :         case arrow::Type::RUN_END_ENCODED:
     588             : #endif
     589             : #if ARROW_VERSION_MAJOR >= 15
     590             :         case arrow::Type::LIST_VIEW:
     591             :         case arrow::Type::LARGE_LIST_VIEW:
     592             : #endif
     593             :         case arrow::Type::MAX_ID:
     594             :         {
     595           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     596             :                      "Field %s of unhandled type %s ignored",
     597           0 :                      field->name().c_str(), type->ToString().c_str());
     598           0 :             break;
     599             :         }
     600             :     }
     601             : 
     602       36638 :     if (bTypeOK)
     603             :     {
     604             :         const auto oIter =
     605       36254 :             oMapFieldNameToGDALSchemaFieldDefn.find(field->name());
     606       36254 :         oField.SetType(eType);
     607       36254 :         if (oIter != oMapFieldNameToGDALSchemaFieldDefn.end())
     608             :         {
     609         389 :             const auto &poGDALFieldDefn = oIter->second;
     610         389 :             if (poGDALFieldDefn->GetType() == eType)
     611             :             {
     612         389 :                 if (eSubType == OFSTNone)
     613             :                 {
     614         277 :                     eSubType = poGDALFieldDefn->GetSubType();
     615             :                 }
     616         112 :                 else if (eSubType != poGDALFieldDefn->GetSubType())
     617             :                 {
     618           0 :                     CPLDebug(
     619           0 :                         GetDriverUCName().c_str(),
     620             :                         "Field subtype inferred from Parquet/Arrow schema is "
     621             :                         "%s, "
     622             :                         "whereas the one in gdal:schema is %s. "
     623             :                         "Using the former one.",
     624             :                         OGR_GetFieldSubTypeName(eSubType),
     625             :                         OGR_GetFieldSubTypeName(poGDALFieldDefn->GetSubType()));
     626             :                 }
     627             :             }
     628             :             else
     629             :             {
     630           0 :                 CPLDebug(GetDriverUCName().c_str(),
     631             :                          "Field type inferred from Parquet/Arrow schema is %s, "
     632             :                          "whereas the one in gdal:schema is %s. "
     633             :                          "Using the former one.",
     634             :                          OGR_GetFieldTypeName(eType),
     635             :                          OGR_GetFieldTypeName(poGDALFieldDefn->GetType()));
     636             :             }
     637         389 :             if (poGDALFieldDefn->GetWidth() > 0)
     638          42 :                 oField.SetWidth(poGDALFieldDefn->GetWidth());
     639         389 :             if (poGDALFieldDefn->GetPrecision() > 0)
     640          18 :                 oField.SetPrecision(poGDALFieldDefn->GetPrecision());
     641         389 :             if (poGDALFieldDefn->GetAlternativeNameRef()[0])
     642           2 :                 oField.SetAlternativeName(
     643             :                     poGDALFieldDefn->GetAlternativeNameRef());
     644         389 :             if (!poGDALFieldDefn->GetComment().empty())
     645           3 :                 oField.SetComment(poGDALFieldDefn->GetComment());
     646             :         }
     647       36254 :         oField.SetSubType(eSubType);
     648       36254 :         oField.SetNullable(field->nullable());
     649       36254 :         m_poFeatureDefn->AddFieldDefn(&oField);
     650       36254 :         m_anMapFieldIndexToArrowColumn.push_back(path);
     651             :     }
     652             : 
     653       73276 :     return bTypeOK;
     654             : }
     655             : 
     656             : /************************************************************************/
     657             : /*                       CreateFieldFromSchema()                        */
     658             : /************************************************************************/
     659             : 
     660       10459 : inline void OGRArrowLayer::CreateFieldFromSchema(
     661             :     const std::shared_ptr<arrow::Field> &field, const std::vector<int> &path,
     662             :     const std::map<std::string, std::unique_ptr<OGRFieldDefn>>
     663             :         &oMapFieldNameToGDALSchemaFieldDefn)
     664             : {
     665       20918 :     OGRFieldDefn oField(field->name().c_str(), OFTString);
     666       10459 :     OGRFieldType eType = OFTString;
     667       10459 :     OGRFieldSubType eSubType = OFSTNone;
     668       10459 :     bool bTypeOK = true;
     669             : 
     670       20918 :     auto type = field->type();
     671       10459 :     if (type->id() == arrow::Type::DICTIONARY && path.size() == 1)
     672             :     {
     673             :         const auto dictionaryType =
     674         220 :             std::static_pointer_cast<arrow::DictionaryType>(field->type());
     675         220 :         auto indexType = dictionaryType->index_type();
     676         220 :         if (dictionaryType->value_type()->id() == arrow::Type::STRING &&
     677         110 :             IsIntegerArrowType(indexType->id()))
     678             :         {
     679         220 :             std::string osDomainName(field->name() + "Domain");
     680         110 :             m_poArrowDS->RegisterDomainName(osDomainName,
     681         110 :                                             m_poFeatureDefn->GetFieldCount());
     682         110 :             oField.SetDomainName(osDomainName);
     683         110 :             type = std::move(indexType);
     684             :         }
     685             :         else
     686             :         {
     687           0 :             bTypeOK = false;
     688             :         }
     689             :     }
     690             : 
     691       10459 :     if (type->id() == arrow::Type::STRUCT)
     692             :     {
     693         424 :         const auto subfields = field->Flatten();
     694             :         const std::string osExtensionName =
     695         424 :             GetFieldExtensionName(field, type, GetDriverUCName().c_str());
     696           2 :         if (osExtensionName == EXTENSION_NAME_ARROW_TIMESTAMP_WITH_OFFSET &&
     697           4 :             subfields.size() == 2 &&
     698           2 :             subfields[0]->name() ==
     699         216 :                 field->name() + "." + ATSWO_TIMESTAMP_FIELD_NAME &&
     700           4 :             subfields[0]->type()->id() == arrow::Type::TIMESTAMP &&
     701           2 :             subfields[1]->name() ==
     702         216 :                 field->name() + "." + ATSWO_OFFSET_MINUTES_FIELD_NAME &&
     703           2 :             subfields[1]->type()->id() == arrow::Type::INT16)
     704             :         {
     705           2 :             oField.SetType(OFTDateTime);
     706           2 :             oField.SetTZFlag(OGR_TZFLAG_MIXED_TZ);
     707           2 :             oField.SetNullable(field->nullable());
     708           2 :             m_poFeatureDefn->AddFieldDefn(&oField);
     709           2 :             m_anMapFieldIndexToArrowColumn.push_back(path);
     710             :         }
     711             :         else
     712             :         {
     713         420 :             auto newpath = path;
     714         210 :             newpath.push_back(0);
     715         946 :             for (int j = 0; j < static_cast<int>(subfields.size()); j++)
     716             :             {
     717         736 :                 const auto &subfield = subfields[j];
     718         736 :                 newpath.back() = j;
     719         736 :                 CreateFieldFromSchema(subfield, newpath,
     720             :                                       oMapFieldNameToGDALSchemaFieldDefn);
     721             :             }
     722             :         }
     723             :     }
     724       10247 :     else if (bTypeOK)
     725             :     {
     726       10247 :         MapArrowTypeToOGR(type, field, oField, eType, eSubType, path,
     727             :                           oMapFieldNameToGDALSchemaFieldDefn);
     728             :     }
     729       10459 : }
     730             : 
     731             : /************************************************************************/
     732             : /*                        BuildDomainFromBatch()                        */
     733             : /************************************************************************/
     734             : 
     735          35 : inline std::unique_ptr<OGRFieldDomain> OGRArrowLayer::BuildDomainFromBatch(
     736             :     const std::string &osDomainName,
     737             :     const std::shared_ptr<arrow::RecordBatch> &poBatch, int iCol) const
     738             : {
     739          70 :     const auto array = poBatch->column(iCol);
     740          70 :     auto castArray = std::static_pointer_cast<arrow::DictionaryArray>(array);
     741          70 :     auto dict = castArray->dictionary();
     742          35 :     CPLAssert(dict->type_id() == arrow::Type::STRING);
     743          35 :     OGRFieldType eType = OFTInteger;
     744          35 :     const auto indexTypeId = castArray->dict_type()->index_type()->id();
     745          35 :     if (indexTypeId == arrow::Type::UINT32 ||
     746          35 :         indexTypeId == arrow::Type::UINT64 || indexTypeId == arrow::Type::INT64)
     747           0 :         eType = OFTInteger64;
     748          70 :     auto values = std::static_pointer_cast<arrow::StringArray>(dict);
     749          70 :     std::vector<OGRCodedValue> asValues;
     750          35 :     if (values->length() > INT_MAX)
     751             :     {
     752           0 :         CPLError(CE_Failure, CPLE_OutOfMemory,
     753             :                  "BuildDomainFromBatch(): too many values");
     754           0 :         return nullptr;
     755             :     }
     756             :     try
     757             :     {
     758          35 :         asValues.reserve(static_cast<size_t>(values->length()));
     759             :     }
     760           0 :     catch (const std::bad_alloc &)
     761             :     {
     762           0 :         CPLError(CE_Failure, CPLE_OutOfMemory,
     763             :                  "BuildDomainFromBatch(): out of memory");
     764           0 :         return nullptr;
     765             :     }
     766         140 :     for (int i = 0; i < static_cast<int>(values->length()); ++i)
     767             :     {
     768         105 :         if (!values->IsNull(i))
     769             :         {
     770             :             OGRCodedValue val;
     771         105 :             val.pszCode = CPLStrdup(CPLSPrintf("%d", i));
     772         105 :             val.pszValue = CPLStrdup(values->GetString(i).c_str());
     773         105 :             asValues.emplace_back(val);
     774             :         }
     775             :     }
     776          70 :     return std::make_unique<OGRCodedFieldDomain>(
     777         105 :         osDomainName, std::string(), eType, OFSTNone, std::move(asValues));
     778             : }
     779             : 
     780             : /************************************************************************/
     781             : /*                          GetStorageArray()                           */
     782             : /************************************************************************/
     783             : 
     784      191214 : static const arrow::Array *GetStorageArray(const arrow::Array *array)
     785             : {
     786      191214 :     if (array->type_id() == arrow::Type::EXTENSION)
     787             :     {
     788             :         auto extensionArray =
     789        1907 :             cpl::down_cast<const arrow::ExtensionArray *>(array);
     790        1907 :         array = extensionArray->storage().get();
     791             :     }
     792      191214 :     return array;
     793             : }
     794             : 
     795             : /************************************************************************/
     796             : /*               ComputeGeometryColumnTypeProcessBatch()                */
     797             : /************************************************************************/
     798             : 
     799         537 : inline OGRwkbGeometryType OGRArrowLayer::ComputeGeometryColumnTypeProcessBatch(
     800             :     const std::shared_ptr<arrow::RecordBatch> &poBatch, int iGeomCol,
     801             :     int iBatchCol, OGRwkbGeometryType eGeomType) const
     802             : {
     803        1074 :     const auto array = poBatch->column(iBatchCol);
     804         537 :     const auto storageArray = GetStorageArray(array.get());
     805             :     const auto castBinaryArray =
     806         537 :         (m_aeGeomEncoding[iGeomCol] == OGRArrowGeomEncoding::WKB)
     807         537 :             ? dynamic_cast<const arrow::BinaryArray *>(storageArray)
     808         537 :             : nullptr;
     809             :     const auto castLargeBinaryArray =
     810         537 :         (m_aeGeomEncoding[iGeomCol] == OGRArrowGeomEncoding::WKB)
     811         537 :             ? dynamic_cast<const arrow::LargeBinaryArray *>(storageArray)
     812         537 :             : nullptr;
     813             :     const auto castStringArray =
     814         537 :         (m_aeGeomEncoding[iGeomCol] == OGRArrowGeomEncoding::WKT)
     815         537 :             ? dynamic_cast<const arrow::StringArray *>(storageArray)
     816         537 :             : nullptr;
     817             :     const auto castLargeStringArray =
     818         537 :         (m_aeGeomEncoding[iGeomCol] == OGRArrowGeomEncoding::WKT)
     819         537 :             ? dynamic_cast<const arrow::LargeStringArray *>(storageArray)
     820         537 :             : nullptr;
     821        3220 :     for (int64_t i = 0; i < poBatch->num_rows(); i++)
     822             :     {
     823        2685 :         if (!storageArray->IsNull(i))
     824             :         {
     825        2160 :             OGRwkbGeometryType eThisGeomType = wkbNone;
     826        2160 :             if (m_aeGeomEncoding[iGeomCol] == OGRArrowGeomEncoding::WKB &&
     827             :                 castBinaryArray)
     828             :             {
     829        2048 :                 arrow::BinaryArray::offset_type out_length = 0;
     830        2048 :                 const uint8_t *data = castBinaryArray->GetValue(i, &out_length);
     831        2048 :                 if (out_length >= 5)
     832             :                 {
     833        2048 :                     OGRReadWKBGeometryType(data, wkbVariantIso, &eThisGeomType);
     834             :                 }
     835             :             }
     836         112 :             else if (m_aeGeomEncoding[iGeomCol] == OGRArrowGeomEncoding::WKB &&
     837             :                      castLargeBinaryArray)
     838             :             {
     839           0 :                 arrow::LargeBinaryArray::offset_type out_length = 0;
     840             :                 const uint8_t *data =
     841           0 :                     castLargeBinaryArray->GetValue(i, &out_length);
     842           0 :                 if (out_length >= 5)
     843             :                 {
     844           0 :                     OGRReadWKBGeometryType(data, wkbVariantIso, &eThisGeomType);
     845             :                 }
     846             :             }
     847         112 :             else if (m_aeGeomEncoding[iGeomCol] == OGRArrowGeomEncoding::WKT &&
     848             :                      castStringArray)
     849             :             {
     850         224 :                 const auto osWKT = castStringArray->GetString(i);
     851         112 :                 if (!osWKT.empty())
     852             :                 {
     853         112 :                     OGRReadWKTGeometryType(osWKT.c_str(), &eThisGeomType);
     854             :                 }
     855             :             }
     856           0 :             else if (m_aeGeomEncoding[iGeomCol] == OGRArrowGeomEncoding::WKT &&
     857             :                      castLargeStringArray)
     858             :             {
     859           0 :                 const auto osWKT = castLargeStringArray->GetString(i);
     860           0 :                 if (!osWKT.empty())
     861             :                 {
     862           0 :                     OGRReadWKTGeometryType(osWKT.c_str(), &eThisGeomType);
     863             :                 }
     864             :             }
     865             : 
     866        2160 :             if (eThisGeomType != wkbNone)
     867             :             {
     868        2160 :                 if (eGeomType == wkbNone)
     869         533 :                     eGeomType = eThisGeomType;
     870        1627 :                 else if (wkbFlatten(eThisGeomType) == wkbFlatten(eGeomType))
     871             :                     ;
     872           6 :                 else if (wkbFlatten(eThisGeomType) == wkbMultiLineString &&
     873           0 :                          wkbFlatten(eGeomType) == wkbLineString)
     874             :                 {
     875           0 :                     eGeomType = OGR_GT_SetModifier(
     876             :                         wkbMultiLineString,
     877           0 :                         OGR_GT_HasZ(eThisGeomType) || OGR_GT_HasZ(eGeomType),
     878           0 :                         OGR_GT_HasM(eThisGeomType) || OGR_GT_HasM(eGeomType));
     879             :                 }
     880           8 :                 else if (wkbFlatten(eThisGeomType) == wkbLineString &&
     881           2 :                          wkbFlatten(eGeomType) == wkbMultiLineString)
     882             :                     ;
     883           6 :                 else if (wkbFlatten(eThisGeomType) == wkbMultiPolygon &&
     884           0 :                          wkbFlatten(eGeomType) == wkbPolygon)
     885             :                 {
     886           0 :                     eGeomType = OGR_GT_SetModifier(
     887             :                         wkbMultiPolygon,
     888           0 :                         OGR_GT_HasZ(eThisGeomType) || OGR_GT_HasZ(eGeomType),
     889           0 :                         OGR_GT_HasM(eThisGeomType) || OGR_GT_HasM(eGeomType));
     890             :                 }
     891          10 :                 else if (wkbFlatten(eThisGeomType) == wkbPolygon &&
     892           4 :                          wkbFlatten(eGeomType) == wkbMultiPolygon)
     893             :                     ;
     894             :                 else
     895           2 :                     return wkbUnknown;
     896             : 
     897        2158 :                 eGeomType = OGR_GT_SetModifier(
     898             :                     eGeomType,
     899        2158 :                     OGR_GT_HasZ(eThisGeomType) || OGR_GT_HasZ(eGeomType),
     900        2158 :                     OGR_GT_HasM(eThisGeomType) || OGR_GT_HasM(eGeomType));
     901             :             }
     902             :         }
     903             :     }
     904         535 :     return eGeomType;
     905             : }
     906             : 
     907             : /************************************************************************/
     908             : /*                            IsPointType()                             */
     909             : /************************************************************************/
     910             : 
     911        1055 : static bool IsPointType(const std::shared_ptr<arrow::DataType> &type,
     912             :                         bool &bHasZOut, bool &bHasMOut)
     913             : {
     914        1055 :     if (type->id() != arrow::Type::FIXED_SIZE_LIST)
     915         725 :         return false;
     916         660 :     auto poListType = std::static_pointer_cast<arrow::FixedSizeListType>(type);
     917         330 :     const int nOutDimensionality = poListType->list_size();
     918         660 :     const std::string osValueFieldName(poListType->value_field()->name());
     919         330 :     if (nOutDimensionality == 2)
     920             :     {
     921         114 :         bHasZOut = false;
     922         114 :         bHasMOut = false;
     923             :     }
     924         216 :     else if (nOutDimensionality == 3)
     925             :     {
     926         156 :         if (osValueFieldName == "xym")
     927             :         {
     928          60 :             bHasZOut = false;
     929          60 :             bHasMOut = true;
     930             :         }
     931             :         else /* if (osValueFieldName == "xyz" || osValueFieldName == "element") */
     932             :         {
     933          96 :             bHasMOut = false;
     934          96 :             bHasZOut = true;
     935             :         }
     936             :     }
     937          60 :     else if (nOutDimensionality == 4)
     938             :     {
     939          60 :         bHasMOut = true;
     940          60 :         bHasZOut = true;
     941             :     }
     942             :     else
     943             :     {
     944           0 :         return false;
     945             :     }
     946         330 :     return poListType->value_type()->id() == arrow::Type::DOUBLE;
     947             : }
     948             : 
     949             : /************************************************************************/
     950             : /*                         IsListOfPointType()                          */
     951             : /************************************************************************/
     952             : 
     953        1672 : static bool IsListOfPointType(const std::shared_ptr<arrow::DataType> &type,
     954             :                               int nDepth, bool &bHasZOut, bool &bHasMOut)
     955             : {
     956        1672 :     if (type->id() != arrow::Type::LIST)
     957           0 :         return false;
     958        1672 :     auto poListType = std::static_pointer_cast<arrow::ListType>(type);
     959             :     return nDepth == 1
     960        1672 :                ? IsPointType(poListType->value_type(), bHasZOut, bHasMOut)
     961         786 :                : IsListOfPointType(poListType->value_type(), nDepth - 1,
     962        1672 :                                    bHasZOut, bHasMOut);
     963             : }
     964             : 
     965             : /************************************************************************/
     966             : /*                         IsPointStructType()                          */
     967             : /************************************************************************/
     968             : 
     969         725 : static bool IsPointStructType(const std::shared_ptr<arrow::DataType> &type,
     970             :                               bool &bHasZOut, bool &bHasMOut)
     971             : {
     972         725 :     if (type->id() != arrow::Type::STRUCT)
     973           0 :         return false;
     974        1450 :     auto poStructType = std::static_pointer_cast<arrow::StructType>(type);
     975         725 :     const int nNumFields = poStructType->num_fields();
     976         725 :     if (nNumFields < 2 || nNumFields > 4)
     977           0 :         return false;
     978         725 :     bHasZOut = false;
     979         725 :     bHasMOut = false;
     980        1450 :     const auto poFieldX = poStructType->field(0);
     981        1450 :     if (poFieldX->name() != "x" ||
     982         725 :         poFieldX->type()->id() != arrow::Type::DOUBLE)
     983           0 :         return false;
     984        1450 :     const auto poFieldY = poStructType->field(1);
     985        1450 :     if (poFieldY->name() != "y" ||
     986         725 :         poFieldY->type()->id() != arrow::Type::DOUBLE)
     987           0 :         return false;
     988         725 :     if (nNumFields == 2)
     989         419 :         return true;
     990         612 :     const auto poField2 = poStructType->field(2);
     991         306 :     if (poField2->type()->id() != arrow::Type::DOUBLE)
     992           0 :         return false;
     993         306 :     if (poField2->name() == "z")
     994             :     {
     995         300 :         bHasZOut = true;
     996         300 :         if (nNumFields == 4)
     997             :         {
     998           6 :             const auto poField3 = poStructType->field(3);
     999          12 :             if (poField3->name() != "m" ||
    1000           6 :                 poField3->type()->id() != arrow::Type::DOUBLE)
    1001           0 :                 return false;
    1002           6 :             bHasMOut = true;
    1003             :         }
    1004             :     }
    1005           6 :     else if (poField2->name() == "m")
    1006             :     {
    1007           6 :         bHasMOut = true;
    1008             :     }
    1009             :     else
    1010             :     {
    1011           0 :         return false;
    1012             :     }
    1013         306 :     return true;
    1014             : }
    1015             : 
    1016             : /************************************************************************/
    1017             : /*                      IsListOfPointStructType()                       */
    1018             : /************************************************************************/
    1019             : 
    1020             : static bool
    1021        1164 : IsListOfPointStructType(const std::shared_ptr<arrow::DataType> &type,
    1022             :                         int nDepth, bool &bHasZOut, bool &bHasMOut)
    1023             : {
    1024        1164 :     if (type->id() != arrow::Type::LIST)
    1025           0 :         return false;
    1026        1164 :     auto poListType = std::static_pointer_cast<arrow::ListType>(type);
    1027             :     return nDepth == 1
    1028        1164 :                ? IsPointStructType(poListType->value_type(), bHasZOut, bHasMOut)
    1029         555 :                : IsListOfPointStructType(poListType->value_type(), nDepth - 1,
    1030        1164 :                                          bHasZOut, bHasMOut);
    1031             : }
    1032             : 
    1033             : /************************************************************************/
    1034             : /*                      IsValidGeometryEncoding()                       */
    1035             : /************************************************************************/
    1036             : 
    1037        1897 : inline bool OGRArrowLayer::IsValidGeometryEncoding(
    1038             :     const std::shared_ptr<arrow::Field> &field, const std::string &osEncoding,
    1039             :     bool bWarnIfUnknownEncoding, OGRwkbGeometryType &eGeomTypeOut,
    1040             :     OGRArrowGeomEncoding &eOGRArrowGeomEncodingOut)
    1041             : {
    1042        1897 :     const auto &fieldName = field->name();
    1043        3794 :     std::shared_ptr<arrow::DataType> fieldType = field->type();
    1044        1897 :     auto fieldTypeId = fieldType->id();
    1045             : 
    1046        1897 :     if (fieldTypeId == arrow::Type::EXTENSION)
    1047             :     {
    1048             :         auto extensionType =
    1049           0 :             cpl::down_cast<arrow::ExtensionType *>(fieldType.get());
    1050           0 :         fieldType = extensionType->storage_type();
    1051           0 :         fieldTypeId = fieldType->id();
    1052             :     }
    1053             : 
    1054        1897 :     eGeomTypeOut = wkbUnknown;
    1055             : 
    1056        3739 :     if (osEncoding == "WKT" ||  // As used in Parquet geo metadata
    1057        1842 :         osEncoding ==
    1058        3739 :             "ogc.wkt" ||  // As used in ARROW:extension:name field metadata
    1059        1842 :         osEncoding ==
    1060             :             "geoarrow.wkt"  // As used in ARROW:extension:name field metadata
    1061             :     )
    1062             :     {
    1063          55 :         if (fieldTypeId != arrow::Type::LARGE_STRING &&
    1064             :             fieldTypeId != arrow::Type::STRING)
    1065             :         {
    1066           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1067             :                      "Geometry column %s has a non String type: %s. "
    1068             :                      "Handling it as a regular field",
    1069           0 :                      fieldName.c_str(), fieldType->ToString().c_str());
    1070           0 :             return false;
    1071             :         }
    1072          55 :         eOGRArrowGeomEncodingOut = OGRArrowGeomEncoding::WKT;
    1073          55 :         return true;
    1074             :     }
    1075             : 
    1076        2900 :     if (osEncoding == "WKB" ||  // As used in Parquet geo metadata
    1077        1058 :         osEncoding ==
    1078        2900 :             "ogc.wkb" ||  // As used in ARROW:extension:name field metadata
    1079        1058 :         osEncoding ==
    1080             :             "geoarrow.wkb"  // As used in ARROW:extension:name field metadata
    1081             :     )
    1082             :     {
    1083         785 :         if (fieldTypeId != arrow::Type::LARGE_BINARY &&
    1084             :             fieldTypeId != arrow::Type::BINARY)
    1085             :         {
    1086           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1087             :                      "Geometry column %s has a non Binary type: %s. "
    1088             :                      "Handling it as a regular field",
    1089           0 :                      fieldName.c_str(), fieldType->ToString().c_str());
    1090           0 :             return false;
    1091             :         }
    1092         785 :         eOGRArrowGeomEncodingOut = OGRArrowGeomEncoding::WKB;
    1093         785 :         return true;
    1094             :     }
    1095             : 
    1096        1057 :     bool bHasZ = false;
    1097        1057 :     bool bHasM = false;
    1098        1057 :     if (osEncoding == "geoarrow.point" || osEncoding == "point")
    1099             :     {
    1100         169 :         if (IsPointType(fieldType, bHasZ, bHasM))
    1101             :         {
    1102          53 :             eOGRArrowGeomEncodingOut = OGRArrowGeomEncoding::GEOARROW_FSL_POINT;
    1103             :         }
    1104         116 :         else if (IsPointStructType(fieldType, bHasZ, bHasM))
    1105             :         {
    1106         116 :             eOGRArrowGeomEncodingOut =
    1107             :                 OGRArrowGeomEncoding::GEOARROW_STRUCT_POINT;
    1108             :         }
    1109             :         else
    1110             :         {
    1111           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1112             :                      "Geometry column %s has a type != fixed_size_list<xy: "
    1113             :                      "double>[2]> and != struct<x: double, y: double>: %s. "
    1114             :                      "Handling it as a regular field",
    1115           0 :                      fieldName.c_str(), fieldType->name().c_str());
    1116           0 :             return false;
    1117             :         }
    1118         169 :         eGeomTypeOut = OGR_GT_SetModifier(wkbPoint, static_cast<int>(bHasZ),
    1119             :                                           static_cast<int>(bHasM));
    1120         169 :         return true;
    1121             :     }
    1122             : 
    1123         888 :     else if (osEncoding == "geoarrow.linestring" || osEncoding == "linestring")
    1124             :     {
    1125         154 :         if (IsListOfPointType(fieldType, 1, bHasZ, bHasM))
    1126             :         {
    1127          52 :             eOGRArrowGeomEncodingOut =
    1128             :                 OGRArrowGeomEncoding::GEOARROW_FSL_LINESTRING;
    1129             :         }
    1130         102 :         else if (IsListOfPointStructType(fieldType, 1, bHasZ, bHasM))
    1131             :         {
    1132         102 :             eOGRArrowGeomEncodingOut =
    1133             :                 OGRArrowGeomEncoding::GEOARROW_STRUCT_LINESTRING;
    1134             :         }
    1135             :         else
    1136             :         {
    1137           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1138             :                      "Geometry column %s has a type != fixed_size_list<xy: "
    1139             :                      "double>[2]> and != list<element: struct<x: double, y: "
    1140             :                      "double>>: %s. "
    1141             :                      "Handling it as a regular field",
    1142           0 :                      fieldName.c_str(), fieldType->ToString().c_str());
    1143           0 :             return false;
    1144             :         }
    1145         154 :         eGeomTypeOut = OGR_GT_SetModifier(
    1146             :             wkbLineString, static_cast<int>(bHasZ), static_cast<int>(bHasM));
    1147         154 :         return true;
    1148             :     }
    1149             : 
    1150         734 :     else if (osEncoding == "geoarrow.polygon" || osEncoding == "polygon")
    1151             :     {
    1152         216 :         if (IsListOfPointType(fieldType, 2, bHasZ, bHasM))
    1153             :         {
    1154          63 :             eOGRArrowGeomEncodingOut =
    1155             :                 OGRArrowGeomEncoding::GEOARROW_FSL_POLYGON;
    1156             :         }
    1157         153 :         else if (IsListOfPointStructType(fieldType, 2, bHasZ, bHasM))
    1158             :         {
    1159         153 :             eOGRArrowGeomEncodingOut =
    1160             :                 OGRArrowGeomEncoding::GEOARROW_STRUCT_POLYGON;
    1161             :         }
    1162             :         else
    1163             :         {
    1164           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1165             :                      "Geometry column %s has a type != list<vertices: "
    1166             :                      "fixed_size_list<xy: double>[2]>> and != list<element: "
    1167             :                      "list<element: struct<x: double, y: double>>>: %s. "
    1168             :                      "Handling it as a regular field",
    1169           0 :                      fieldName.c_str(), fieldType->ToString().c_str());
    1170           0 :             return false;
    1171             :         }
    1172         216 :         eGeomTypeOut = OGR_GT_SetModifier(wkbPolygon, static_cast<int>(bHasZ),
    1173             :                                           static_cast<int>(bHasM));
    1174         216 :         return true;
    1175             :     }
    1176             : 
    1177         518 :     else if (osEncoding == "geoarrow.multipoint" || osEncoding == "multipoint")
    1178             :     {
    1179         154 :         if (IsListOfPointType(fieldType, 1, bHasZ, bHasM))
    1180             :         {
    1181          52 :             eOGRArrowGeomEncodingOut =
    1182             :                 OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOINT;
    1183             :         }
    1184         102 :         else if (IsListOfPointStructType(fieldType, 1, bHasZ, bHasM))
    1185             :         {
    1186         102 :             eOGRArrowGeomEncodingOut =
    1187             :                 OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOINT;
    1188             :         }
    1189             :         else
    1190             :         {
    1191           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1192             :                      "Geometry column %s has a type != fixed_size_list<xy: "
    1193             :                      "double>[2]> and != list<element: struct<x: double, y: "
    1194             :                      "double>>: %s. "
    1195             :                      "Handling it as a regular field",
    1196           0 :                      fieldName.c_str(), fieldType->ToString().c_str());
    1197           0 :             return false;
    1198             :         }
    1199         154 :         eGeomTypeOut = OGR_GT_SetModifier(
    1200             :             wkbMultiPoint, static_cast<int>(bHasZ), static_cast<int>(bHasM));
    1201         154 :         return true;
    1202             :     }
    1203             : 
    1204         676 :     else if (osEncoding == "geoarrow.multilinestring" ||
    1205         312 :              osEncoding == "multilinestring")
    1206             :     {
    1207         154 :         if (IsListOfPointType(fieldType, 2, bHasZ, bHasM))
    1208             :         {
    1209          52 :             eOGRArrowGeomEncodingOut =
    1210             :                 OGRArrowGeomEncoding::GEOARROW_FSL_MULTILINESTRING;
    1211             :         }
    1212         102 :         else if (IsListOfPointStructType(fieldType, 2, bHasZ, bHasM))
    1213             :         {
    1214         102 :             eOGRArrowGeomEncodingOut =
    1215             :                 OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTILINESTRING;
    1216             :         }
    1217             :         else
    1218             :         {
    1219           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1220             :                      "Geometry column %s has a type != list<vertices: "
    1221             :                      "fixed_size_list<xy: double>[2]>> and != list<element: "
    1222             :                      "list<element: struct<x: double, y: double>>>: %s. "
    1223             :                      "Handling it as a regular field",
    1224           0 :                      fieldName.c_str(), fieldType->ToString().c_str());
    1225           0 :             return false;
    1226             :         }
    1227         154 :         eGeomTypeOut =
    1228         154 :             OGR_GT_SetModifier(wkbMultiLineString, static_cast<int>(bHasZ),
    1229             :                                static_cast<int>(bHasM));
    1230         154 :         return true;
    1231             :     }
    1232             : 
    1233         362 :     else if (osEncoding == "geoarrow.multipolygon" ||
    1234         152 :              osEncoding == "multipolygon")
    1235             :     {
    1236         208 :         if (IsListOfPointType(fieldType, 3, bHasZ, bHasM))
    1237             :         {
    1238          58 :             eOGRArrowGeomEncodingOut =
    1239             :                 OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOLYGON;
    1240             :         }
    1241         150 :         else if (IsListOfPointStructType(fieldType, 3, bHasZ, bHasM))
    1242             :         {
    1243         150 :             eOGRArrowGeomEncodingOut =
    1244             :                 OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOLYGON;
    1245             :         }
    1246             :         else
    1247             :         {
    1248           0 :             CPLError(
    1249             :                 CE_Warning, CPLE_AppDefined,
    1250             :                 "Geometry column %s has a type != list<polygons: list<rings: "
    1251             :                 "list<vertices: fixed_size_list<xy: double>[2]>>> and != "
    1252             :                 "list<element: list<element: list<element: struct<x: double, "
    1253             :                 "y: double>>>>: %s. "
    1254             :                 "Handling it as a regular field",
    1255           0 :                 fieldName.c_str(), fieldType->ToString().c_str());
    1256           0 :             return false;
    1257             :         }
    1258         208 :         eGeomTypeOut = OGR_GT_SetModifier(
    1259             :             wkbMultiPolygon, static_cast<int>(bHasZ), static_cast<int>(bHasM));
    1260         208 :         return true;
    1261             :     }
    1262             : 
    1263           2 :     if (bWarnIfUnknownEncoding)
    1264             :     {
    1265           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    1266             :                  "Geometry column %s uses a unhandled encoding: %s. "
    1267             :                  "Handling it as a regular field",
    1268             :                  fieldName.c_str(), osEncoding.c_str());
    1269             :     }
    1270           2 :     return false;
    1271             : }
    1272             : 
    1273             : /************************************************************************/
    1274             : /*                     GetGeometryTypeFromString()                      */
    1275             : /************************************************************************/
    1276             : 
    1277             : inline OGRwkbGeometryType
    1278         289 : OGRArrowLayer::GetGeometryTypeFromString(const std::string &osType)
    1279             : {
    1280         289 :     OGRwkbGeometryType eGeomType = wkbUnknown;
    1281         289 :     OGRReadWKTGeometryType(osType.c_str(), &eGeomType);
    1282         289 :     if (eGeomType == wkbUnknown && !osType.empty())
    1283             :     {
    1284           0 :         CPLDebug("ARROW", "Unknown geometry type: %s", osType.c_str());
    1285             :     }
    1286         289 :     return eGeomType;
    1287             : }
    1288             : 
    1289             : static CPLJSONObject GetObjectAsJSON(const arrow::Array *array,
    1290             :                                      const size_t nIdx);
    1291             : 
    1292             : /************************************************************************/
    1293             : /*                             AddToArray()                             */
    1294             : /************************************************************************/
    1295             : 
    1296             : template <class Container>
    1297       27798 : void AddToContainer(Container &oContainer, const arrow::Array *array,
    1298             :                     const size_t nIdx)
    1299             : {
    1300       27798 :     switch (array->type()->id())
    1301             :     {
    1302         758 :         case arrow::Type::BOOL:
    1303             :         {
    1304         758 :             oContainer.Add(
    1305         758 :                 static_cast<const arrow::BooleanArray *>(array)->Value(nIdx));
    1306         758 :             break;
    1307             :         }
    1308         765 :         case arrow::Type::UINT8:
    1309             :         {
    1310         765 :             oContainer.Add(
    1311         765 :                 static_cast<const arrow::UInt8Array *>(array)->Value(nIdx));
    1312         765 :             break;
    1313             :         }
    1314         765 :         case arrow::Type::INT8:
    1315             :         {
    1316         765 :             oContainer.Add(
    1317         765 :                 static_cast<const arrow::Int8Array *>(array)->Value(nIdx));
    1318         765 :             break;
    1319             :         }
    1320         765 :         case arrow::Type::UINT16:
    1321             :         {
    1322         765 :             oContainer.Add(
    1323         765 :                 static_cast<const arrow::UInt16Array *>(array)->Value(nIdx));
    1324         765 :             break;
    1325             :         }
    1326         765 :         case arrow::Type::INT16:
    1327             :         {
    1328         765 :             oContainer.Add(
    1329         765 :                 static_cast<const arrow::Int16Array *>(array)->Value(nIdx));
    1330         765 :             break;
    1331             :         }
    1332         765 :         case arrow::Type::INT32:
    1333             :         {
    1334         765 :             oContainer.Add(
    1335         736 :                 static_cast<const arrow::Int32Array *>(array)->Value(nIdx));
    1336         765 :             break;
    1337             :         }
    1338         137 :         case arrow::Type::UINT32:
    1339             :         {
    1340         137 :             oContainer.Add(static_cast<GInt64>(
    1341         137 :                 static_cast<const arrow::UInt32Array *>(array)->Value(nIdx)));
    1342         137 :             break;
    1343             :         }
    1344        4984 :         case arrow::Type::INT64:
    1345             :         {
    1346        4984 :             oContainer.Add(static_cast<GInt64>(
    1347        4984 :                 static_cast<const arrow::Int64Array *>(array)->Value(nIdx)));
    1348        4984 :             break;
    1349             :         }
    1350         765 :         case arrow::Type::UINT64:
    1351             :         {
    1352         765 :             oContainer.Add(static_cast<uint64_t>(
    1353         765 :                 static_cast<const arrow::UInt64Array *>(array)->Value(nIdx)));
    1354         765 :             break;
    1355             :         }
    1356         350 :         case arrow::Type::HALF_FLOAT:
    1357             :         {
    1358             :             const uint16_t nFloat16 =
    1359         350 :                 static_cast<const arrow::HalfFloatArray *>(array)->Value(nIdx);
    1360         350 :             uint32_t nFloat32 = CPLHalfToFloat(nFloat16);
    1361             :             float f;
    1362         350 :             memcpy(&f, &nFloat32, sizeof(nFloat32));
    1363         350 :             oContainer.Add(f);
    1364         350 :             break;
    1365             :         }
    1366         768 :         case arrow::Type::FLOAT:
    1367             :         {
    1368         768 :             oContainer.Add(
    1369         768 :                 static_cast<const arrow::FloatArray *>(array)->Value(nIdx));
    1370         768 :             break;
    1371             :         }
    1372        4338 :         case arrow::Type::DOUBLE:
    1373             :         {
    1374        4338 :             oContainer.Add(
    1375        4310 :                 static_cast<const arrow::DoubleArray *>(array)->Value(nIdx));
    1376        4338 :             break;
    1377             :         }
    1378             : 
    1379             : #if ARROW_VERSION_MAJOR >= 18
    1380           0 :         case arrow::Type::DECIMAL32:
    1381             :         {
    1382           0 :             oContainer.Add(
    1383           0 :                 CPLAtof(static_cast<const arrow::Decimal32Array *>(array)
    1384             :                             ->FormatValue(nIdx)
    1385             :                             .c_str()));
    1386           0 :             break;
    1387             :         }
    1388           0 :         case arrow::Type::DECIMAL64:
    1389             :         {
    1390           0 :             oContainer.Add(
    1391           0 :                 CPLAtof(static_cast<const arrow::Decimal64Array *>(array)
    1392             :                             ->FormatValue(nIdx)
    1393             :                             .c_str()));
    1394           0 :             break;
    1395             :         }
    1396             : #endif
    1397        1364 :         case arrow::Type::DECIMAL128:
    1398             :         {
    1399        1364 :             oContainer.Add(
    1400        1356 :                 CPLAtof(static_cast<const arrow::Decimal128Array *>(array)
    1401             :                             ->FormatValue(nIdx)
    1402             :                             .c_str()));
    1403        1364 :             break;
    1404             :         }
    1405         118 :         case arrow::Type::DECIMAL256:
    1406             :         {
    1407         118 :             oContainer.Add(
    1408         110 :                 CPLAtof(static_cast<const arrow::Decimal256Array *>(array)
    1409             :                             ->FormatValue(nIdx)
    1410             :                             .c_str()));
    1411         118 :             break;
    1412             :         }
    1413        3692 :         case arrow::Type::STRING:
    1414             :         {
    1415        3692 :             oContainer.Add(
    1416             :                 static_cast<const arrow::StringArray *>(array)->GetString(
    1417             :                     nIdx));
    1418        3692 :             break;
    1419             :         }
    1420         118 :         case arrow::Type::LARGE_STRING:
    1421             :         {
    1422         118 :             oContainer.Add(
    1423             :                 static_cast<const arrow::LargeStringArray *>(array)->GetString(
    1424             :                     nIdx));
    1425         118 :             break;
    1426             :         }
    1427             : #if ARROW_VERSION_MAJOR >= 15
    1428           4 :         case arrow::Type::STRING_VIEW:
    1429             :         {
    1430           4 :             oContainer.Add(
    1431             :                 static_cast<const arrow::StringViewArray *>(array)->GetString(
    1432             :                     nIdx));
    1433           4 :             break;
    1434             :         }
    1435             : #endif
    1436        6573 :         case arrow::Type::LIST:
    1437             :         case arrow::Type::LARGE_LIST:
    1438             :         case arrow::Type::FIXED_SIZE_LIST:
    1439             :         case arrow::Type::MAP:
    1440             :         case arrow::Type::STRUCT:
    1441             :         {
    1442        6573 :             oContainer.Add(GetObjectAsJSON(array, nIdx));
    1443        6573 :             break;
    1444             :         }
    1445             : 
    1446           4 :         case arrow::Type::BINARY:
    1447             :         {
    1448           4 :             const auto castArray =
    1449             :                 static_cast<const arrow::BinaryArray *>(array);
    1450           4 :             int length = 0;
    1451           4 :             const uint8_t *data = castArray->GetValue(nIdx, &length);
    1452           4 :             bool bIsASCII = true;
    1453           9 :             for (int i = 0; i < length && bIsASCII; ++i)
    1454           5 :                 bIsASCII = data[i] >= 32 && data[i] <= 127;
    1455           4 :             if (bIsASCII)
    1456             :             {
    1457           3 :                 oContainer.Add(
    1458             :                     std::string(reinterpret_cast<const char *>(data), length));
    1459             :             }
    1460             :             else
    1461             :             {
    1462           1 :                 char *pszBase64 = CPLBase64Encode(length, data);
    1463           1 :                 oContainer.Add(std::string("base64:").append(pszBase64));
    1464           1 :                 CPLFree(pszBase64);
    1465             :             }
    1466           4 :             break;
    1467             :         }
    1468             : 
    1469             : #if ARROW_VERSION_MAJOR >= 15
    1470           0 :         case arrow::Type::BINARY_VIEW:
    1471             :         {
    1472           0 :             const auto castArray =
    1473             :                 static_cast<const arrow::BinaryViewArray *>(array);
    1474           0 :             const auto view = castArray->GetView(nIdx);
    1475           0 :             if (view.size() <= INT_MAX)
    1476             :             {
    1477           0 :                 const int length = static_cast<int>(view.size());
    1478           0 :                 const char *data = view.data();
    1479           0 :                 bool bIsASCII = true;
    1480           0 :                 for (int i = 0; i < length && bIsASCII; ++i)
    1481           0 :                     bIsASCII =
    1482           0 :                         data[i] >= 32 && static_cast<unsigned>(data[i]) <= 127;
    1483           0 :                 if (bIsASCII)
    1484             :                 {
    1485           0 :                     oContainer.Add(std::string(view));
    1486             :                 }
    1487             :                 else
    1488             :                 {
    1489           0 :                     char *pszBase64 = CPLBase64Encode(
    1490             :                         length, reinterpret_cast<const GByte *>(data));
    1491           0 :                     oContainer.Add(std::string("base64:").append(pszBase64));
    1492           0 :                     CPLFree(pszBase64);
    1493             :                 }
    1494             :             }
    1495             :             else
    1496             :             {
    1497           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Too large binary view");
    1498             :             }
    1499           0 :             break;
    1500             :         }
    1501             : #endif
    1502             : 
    1503           0 :         default:
    1504             :         {
    1505           0 :             CPLDebug("ARROW", "AddToContainer(): unexpected data type %s",
    1506           0 :                      array->type()->ToString().c_str());
    1507           0 :             break;
    1508             :         }
    1509             :     }
    1510       27798 : }
    1511             : 
    1512             : /************************************************************************/
    1513             : /*                             AddToArray()                             */
    1514             : /************************************************************************/
    1515             : 
    1516        6739 : static void AddToArray(CPLJSONArray &oArray, const arrow::Array *array,
    1517             :                        const size_t nIdx)
    1518             : {
    1519        6739 :     AddToContainer(oArray, array, nIdx);
    1520        6739 : }
    1521             : 
    1522             : /************************************************************************/
    1523             : /*                           GetListAsJSON()                            */
    1524             : /************************************************************************/
    1525             : 
    1526             : template <class ArrowType>
    1527        5194 : static CPLJSONArray GetListAsJSON(const ArrowType *array,
    1528             :                                   const size_t nIdxInArray)
    1529             : {
    1530       10388 :     const auto values = array->values();
    1531        5194 :     const auto nIdxStart = array->value_offset(nIdxInArray);
    1532        5194 :     const auto nCount = array->value_length(nIdxInArray);
    1533        5194 :     CPLJSONArray oArray;
    1534       14357 :     for (auto k = decltype(nCount){0}; k < nCount; k++)
    1535             :     {
    1536        9163 :         if (values->IsNull(nIdxStart + k))
    1537        2424 :             oArray.AddNull();
    1538             :         else
    1539        6739 :             AddToArray(oArray, values.get(),
    1540        6739 :                        static_cast<size_t>(nIdxStart + k));
    1541             :     }
    1542       10388 :     return oArray;
    1543             : }
    1544             : 
    1545             : /************************************************************************/
    1546             : /*                             AddToDict()                              */
    1547             : /************************************************************************/
    1548             : 
    1549             : namespace
    1550             : {
    1551             : struct ContainerAdapter
    1552             : {
    1553             :     CPLJSONObject &m_oDict;
    1554             :     const std::string m_osKey;
    1555             : 
    1556       21059 :     ContainerAdapter(CPLJSONObject &oDictIn, const std::string &osKeyIn)
    1557       21059 :         : m_oDict(oDictIn), m_osKey(osKeyIn)
    1558             :     {
    1559       21059 :     }
    1560             : 
    1561       21059 :     template <class T> void Add(const T &v)
    1562             :     {
    1563       21059 :         m_oDict.Add(m_osKey, v);
    1564       21059 :     }
    1565             : };
    1566             : }  // namespace
    1567             : 
    1568       21059 : static void AddToDict(CPLJSONObject &oDict, const std::string &osKey,
    1569             :                       const arrow::Array *array, const size_t nIdx)
    1570             : {
    1571       42118 :     ContainerAdapter dictAdapter(oDict, osKey);
    1572       21059 :     AddToContainer(dictAdapter, array, nIdx);
    1573       21059 : }
    1574             : 
    1575             : /************************************************************************/
    1576             : /*                            GetMapAsJSON()                            */
    1577             : /************************************************************************/
    1578             : 
    1579             : template <class KeyArrayType>
    1580       25331 : static CPLJSONObject GetMapAsJSON(const arrow::Array *array,
    1581             :                                   const size_t nIdxInArray)
    1582             : {
    1583       25331 :     const auto mapArray = static_cast<const arrow::MapArray *>(array);
    1584       50662 :     const auto keys = std::static_pointer_cast<KeyArrayType>(mapArray->keys());
    1585       50662 :     const auto values = mapArray->items();
    1586       25331 :     const auto nIdxStart = mapArray->value_offset(nIdxInArray);
    1587       25331 :     const int nCount = mapArray->value_length(nIdxInArray);
    1588       25331 :     CPLJSONObject oRoot;
    1589       46079 :     for (int k = 0; k < nCount; k++)
    1590             :     {
    1591       20748 :         if (!keys->IsNull(nIdxStart + k))
    1592             :         {
    1593       41496 :             const auto osKey = keys->GetString(nIdxStart + k);
    1594       20748 :             if (!values->IsNull(nIdxStart + k))
    1595       13807 :                 AddToDict(oRoot, osKey, values.get(), nIdxStart + k);
    1596             :             else
    1597        6941 :                 oRoot.AddNull(osKey);
    1598             :         }
    1599             :     }
    1600       50662 :     return oRoot;
    1601             : }
    1602             : 
    1603       25331 : static CPLJSONObject GetMapAsJSON(const arrow::Array *array,
    1604             :                                   const size_t nIdxInArray)
    1605             : {
    1606       25331 :     const auto mapArray = static_cast<const arrow::MapArray *>(array);
    1607       25331 :     const auto eKeyType = mapArray->keys()->type()->id();
    1608       25331 :     if (eKeyType == arrow::Type::STRING)
    1609       25329 :         return GetMapAsJSON<arrow::StringArray>(array, nIdxInArray);
    1610             : #if ARROW_VERSION_MAJOR >= 15
    1611           2 :     else if (eKeyType == arrow::Type::STRING_VIEW)
    1612           2 :         return GetMapAsJSON<arrow::StringViewArray>(array, nIdxInArray);
    1613             : #endif
    1614             :     else
    1615             :     {
    1616           0 :         CPLAssert(false);
    1617             :         return CPLJSONObject();
    1618             :     }
    1619             : }
    1620             : 
    1621             : /************************************************************************/
    1622             : /*                         GetStructureAsJSON()                         */
    1623             : /************************************************************************/
    1624             : 
    1625        3610 : static CPLJSONObject GetStructureAsJSON(const arrow::Array *array,
    1626             :                                         const size_t nIdxInArray)
    1627             : {
    1628        3610 :     CPLJSONObject oRoot;
    1629        3610 :     const auto structArray = static_cast<const arrow::StructArray *>(array);
    1630        7220 :     const auto structArrayType = structArray->type();
    1631       14519 :     for (int i = 0; i < structArrayType->num_fields(); ++i)
    1632             :     {
    1633       21818 :         const auto field = structArray->field(i);
    1634       10909 :         if (!field->IsNull(nIdxInArray))
    1635             :         {
    1636        7252 :             AddToDict(oRoot, structArrayType->field(i)->name(), field.get(),
    1637             :                       nIdxInArray);
    1638             :         }
    1639             :         else
    1640        3657 :             oRoot.AddNull(structArrayType->field(i)->name());
    1641             :     }
    1642             : 
    1643        7220 :     return oRoot;
    1644             : }
    1645             : 
    1646             : /************************************************************************/
    1647             : /*                          GetObjectAsJSON()                           */
    1648             : /************************************************************************/
    1649             : 
    1650        6573 : static CPLJSONObject GetObjectAsJSON(const arrow::Array *array,
    1651             :                                      const size_t nIdxInArray)
    1652             : {
    1653        6573 :     switch (array->type()->id())
    1654             :     {
    1655          26 :         case arrow::Type::MAP:
    1656          26 :             return GetMapAsJSON(array, nIdxInArray);
    1657        2641 :         case arrow::Type::LIST:
    1658        5282 :             return GetListAsJSON(static_cast<const arrow::ListArray *>(array),
    1659        2641 :                                  nIdxInArray);
    1660         113 :         case arrow::Type::LARGE_LIST:
    1661         226 :             return GetListAsJSON(
    1662         113 :                 static_cast<const arrow::LargeListArray *>(array), nIdxInArray);
    1663         183 :         case arrow::Type::FIXED_SIZE_LIST:
    1664         366 :             return GetListAsJSON(
    1665             :                 static_cast<const arrow::FixedSizeListArray *>(array),
    1666         183 :                 nIdxInArray);
    1667        3610 :         case arrow::Type::STRUCT:
    1668        3610 :             return GetStructureAsJSON(array, nIdxInArray);
    1669           0 :         default:
    1670             :         {
    1671           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1672             :                      "GetObjectAsJSON(): unhandled value format: %s",
    1673           0 :                      array->type()->ToString().c_str());
    1674           0 :             return CPLJSONObject();
    1675             :         }
    1676             :     }
    1677             : }
    1678             : 
    1679             : template <class ArrowType>
    1680       48215 : static int GetListLength(const ArrowType *array, int64_t nIdxInArray)
    1681             : {
    1682       48215 :     const auto nCount = array->value_length(nIdxInArray);
    1683             :     if constexpr (!std::is_same_v<decltype(nCount), int>)
    1684             :     {
    1685             :         // Should be >, but >= makes coverity scan happy
    1686       48215 :         if (nCount >= INT_MAX)
    1687             :         {
    1688           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1689             :                      "More than %d values in list. Clamping to it", INT_MAX);
    1690           0 :             return INT_MAX;
    1691             :         }
    1692             :     }
    1693       48215 :     return static_cast<int>(nCount);
    1694             : }
    1695             : 
    1696             : template <class OGRType, class ArrowType, class ArrayType>
    1697       32688 : static void ReadList(OGRFeature *poFeature, int i, int64_t nIdxInArray,
    1698             :                      const ArrayType *array)
    1699             : {
    1700       65376 :     const auto values = std::static_pointer_cast<ArrowType>(array->values());
    1701       32688 :     const auto nIdxStart = array->value_offset(nIdxInArray);
    1702       32688 :     const int nCount = GetListLength(array, nIdxInArray);
    1703       65376 :     std::vector<OGRType> aValues;
    1704       32688 :     aValues.reserve(nCount);
    1705       97861 :     for (int k = 0; k < nCount; k++)
    1706             :     {
    1707       65173 :         aValues.push_back(static_cast<OGRType>(values->Value(nIdxStart + k)));
    1708             :     }
    1709       32688 :     poFeature->SetField(i, nCount, aValues.data());
    1710       32688 : }
    1711             : 
    1712             : template <class ArrowType, class ArrayType>
    1713        7299 : static void ReadListDouble(OGRFeature *poFeature, int i, int64_t nIdxInArray,
    1714             :                            const ArrayType *array)
    1715             : {
    1716       14598 :     const auto values = std::static_pointer_cast<ArrowType>(array->values());
    1717        7299 :     const auto rawValues = values->raw_values();
    1718        7299 :     const auto nIdxStart = array->value_offset(nIdxInArray);
    1719        7299 :     const int nCount = GetListLength(array, nIdxInArray);
    1720       14598 :     std::vector<double> aValues;
    1721        7299 :     aValues.reserve(nCount);
    1722       21480 :     for (int k = 0; k < nCount; k++)
    1723             :     {
    1724       14181 :         if (values->IsNull(nIdxStart + k))
    1725        2982 :             aValues.push_back(std::numeric_limits<double>::quiet_NaN());
    1726             :         else
    1727       11199 :             aValues.push_back(rawValues[nIdxStart + k]);
    1728             :     }
    1729        7299 :     poFeature->SetField(i, nCount, aValues.data());
    1730        7299 : }
    1731             : 
    1732             : template <class ArrayType>
    1733       50304 : static void ReadList(OGRFeature *poFeature, int i, int64_t nIdxInArray,
    1734             :                      const ArrayType *array, arrow::Type::type valueTypeId)
    1735             : {
    1736       50304 :     switch (valueTypeId)
    1737             :     {
    1738        3442 :         case arrow::Type::BOOL:
    1739             :         {
    1740        3442 :             ReadList<int, arrow::BooleanArray>(poFeature, i, nIdxInArray,
    1741             :                                                array);
    1742        3442 :             break;
    1743             :         }
    1744        3272 :         case arrow::Type::UINT8:
    1745             :         {
    1746        3272 :             ReadList<int, arrow::UInt8Array>(poFeature, i, nIdxInArray, array);
    1747        3272 :             break;
    1748             :         }
    1749        3272 :         case arrow::Type::INT8:
    1750             :         {
    1751        3272 :             ReadList<int, arrow::Int8Array>(poFeature, i, nIdxInArray, array);
    1752        3272 :             break;
    1753             :         }
    1754        3272 :         case arrow::Type::UINT16:
    1755             :         {
    1756        3272 :             ReadList<int, arrow::UInt16Array>(poFeature, i, nIdxInArray, array);
    1757        3272 :             break;
    1758             :         }
    1759        3272 :         case arrow::Type::INT16:
    1760             :         {
    1761        3272 :             ReadList<int, arrow::Int16Array>(poFeature, i, nIdxInArray, array);
    1762        3272 :             break;
    1763             :         }
    1764        4133 :         case arrow::Type::INT32:
    1765             :         {
    1766        4133 :             ReadList<int, arrow::Int32Array>(poFeature, i, nIdxInArray, array);
    1767        4133 :             break;
    1768             :         }
    1769         440 :         case arrow::Type::UINT32:
    1770             :         {
    1771         440 :             ReadList<GIntBig, arrow::UInt32Array>(poFeature, i, nIdxInArray,
    1772             :                                                   array);
    1773         440 :             break;
    1774             :         }
    1775        8313 :         case arrow::Type::INT64:
    1776             :         {
    1777        8313 :             ReadList<GIntBig, arrow::Int64Array>(poFeature, i, nIdxInArray,
    1778             :                                                  array);
    1779        8313 :             break;
    1780             :         }
    1781        3272 :         case arrow::Type::UINT64:
    1782             :         {
    1783        3272 :             ReadList<double, arrow::UInt64Array>(poFeature, i, nIdxInArray,
    1784             :                                                  array);
    1785        3272 :             break;
    1786             :         }
    1787         199 :         case arrow::Type::HALF_FLOAT:
    1788             :         {
    1789         398 :             const auto values = std::static_pointer_cast<arrow::HalfFloatArray>(
    1790             :                 array->values());
    1791         199 :             const auto nIdxStart = array->value_offset(nIdxInArray);
    1792         199 :             const int nCount = GetListLength(array, nIdxInArray);
    1793         398 :             std::vector<double> aValues;
    1794         199 :             aValues.reserve(nCount);
    1795         560 :             for (int k = 0; k < nCount; k++)
    1796             :             {
    1797         361 :                 if (values->IsNull(nIdxStart + k))
    1798         131 :                     aValues.push_back(std::numeric_limits<double>::quiet_NaN());
    1799             :                 else
    1800             :                 {
    1801         230 :                     const uint16_t nFloat16 = values->Value(nIdxStart + k);
    1802         230 :                     uint32_t nFloat32 = CPLHalfToFloat(nFloat16);
    1803             :                     float f;
    1804         230 :                     memcpy(&f, &nFloat32, sizeof(nFloat32));
    1805         230 :                     aValues.push_back(f);
    1806             :                 }
    1807             :             }
    1808         199 :             poFeature->SetField(i, nCount, aValues.data());
    1809         199 :             break;
    1810             :         }
    1811        3503 :         case arrow::Type::FLOAT:
    1812             :         {
    1813        3503 :             ReadListDouble<arrow::FloatArray>(poFeature, i, nIdxInArray, array);
    1814        3503 :             break;
    1815             :         }
    1816        3796 :         case arrow::Type::DOUBLE:
    1817             :         {
    1818        3796 :             ReadListDouble<arrow::DoubleArray>(poFeature, i, nIdxInArray,
    1819             :                                                array);
    1820        3796 :             break;
    1821             :         }
    1822             : 
    1823             : #if ARROW_VERSION_MAJOR >= 18
    1824           0 :         case arrow::Type::DECIMAL32:
    1825             :         {
    1826           0 :             const auto values = std::static_pointer_cast<arrow::Decimal32Array>(
    1827             :                 array->values());
    1828           0 :             const auto nIdxStart = array->value_offset(nIdxInArray);
    1829           0 :             const int nCount = GetListLength(array, nIdxInArray);
    1830           0 :             std::vector<double> aValues;
    1831           0 :             aValues.reserve(nCount);
    1832           0 :             for (int k = 0; k < nCount; k++)
    1833             :             {
    1834           0 :                 if (values->IsNull(nIdxStart + k))
    1835           0 :                     aValues.push_back(std::numeric_limits<double>::quiet_NaN());
    1836             :                 else
    1837           0 :                     aValues.push_back(
    1838           0 :                         CPLAtof(values->FormatValue(nIdxStart + k).c_str()));
    1839             :             }
    1840           0 :             poFeature->SetField(i, nCount, aValues.data());
    1841           0 :             break;
    1842             :         }
    1843             : 
    1844           0 :         case arrow::Type::DECIMAL64:
    1845             :         {
    1846           0 :             const auto values = std::static_pointer_cast<arrow::Decimal64Array>(
    1847             :                 array->values());
    1848           0 :             const auto nIdxStart = array->value_offset(nIdxInArray);
    1849           0 :             const int nCount = GetListLength(array, nIdxInArray);
    1850           0 :             std::vector<double> aValues;
    1851           0 :             aValues.reserve(nCount);
    1852           0 :             for (int k = 0; k < nCount; k++)
    1853             :             {
    1854           0 :                 if (values->IsNull(nIdxStart + k))
    1855           0 :                     aValues.push_back(std::numeric_limits<double>::quiet_NaN());
    1856             :                 else
    1857           0 :                     aValues.push_back(
    1858           0 :                         CPLAtof(values->FormatValue(nIdxStart + k).c_str()));
    1859             :             }
    1860           0 :             poFeature->SetField(i, nCount, aValues.data());
    1861           0 :             break;
    1862             :         }
    1863             : #endif
    1864             : 
    1865        1491 :         case arrow::Type::DECIMAL128:
    1866             :         {
    1867        2982 :             const auto values =
    1868             :                 std::static_pointer_cast<arrow::Decimal128Array>(
    1869             :                     array->values());
    1870        1491 :             const auto nIdxStart = array->value_offset(nIdxInArray);
    1871        1491 :             const int nCount = GetListLength(array, nIdxInArray);
    1872        2982 :             std::vector<double> aValues;
    1873        1491 :             aValues.reserve(nCount);
    1874        2982 :             for (int k = 0; k < nCount; k++)
    1875             :             {
    1876        1491 :                 if (values->IsNull(nIdxStart + k))
    1877         348 :                     aValues.push_back(std::numeric_limits<double>::quiet_NaN());
    1878             :                 else
    1879        1143 :                     aValues.push_back(
    1880        1143 :                         CPLAtof(values->FormatValue(nIdxStart + k).c_str()));
    1881             :             }
    1882        1491 :             poFeature->SetField(i, nCount, aValues.data());
    1883        1491 :             break;
    1884             :         }
    1885             : 
    1886        1491 :         case arrow::Type::DECIMAL256:
    1887             :         {
    1888        2982 :             const auto values =
    1889             :                 std::static_pointer_cast<arrow::Decimal256Array>(
    1890             :                     array->values());
    1891        1491 :             const auto nIdxStart = array->value_offset(nIdxInArray);
    1892        1491 :             const int nCount = GetListLength(array, nIdxInArray);
    1893        2982 :             std::vector<double> aValues;
    1894        1491 :             aValues.reserve(nCount);
    1895        2982 :             for (int k = 0; k < nCount; k++)
    1896             :             {
    1897        1491 :                 if (values->IsNull(nIdxStart + k))
    1898         348 :                     aValues.push_back(std::numeric_limits<double>::quiet_NaN());
    1899             :                 else
    1900        1143 :                     aValues.push_back(
    1901        1143 :                         CPLAtof(values->FormatValue(nIdxStart + k).c_str()));
    1902             :             }
    1903        1491 :             poFeature->SetField(i, nCount, aValues.data());
    1904        1491 :             break;
    1905             :         }
    1906             : 
    1907        3552 :         case arrow::Type::STRING:
    1908             :         {
    1909        7104 :             const auto values =
    1910             :                 std::static_pointer_cast<arrow::StringArray>(array->values());
    1911        3552 :             const auto nIdxStart = array->value_offset(nIdxInArray);
    1912        3552 :             const int nCount = GetListLength(array, nIdxInArray);
    1913        7104 :             CPLStringList aosList;
    1914        9255 :             for (int k = 0; k < nCount; k++)
    1915             :             {
    1916        5703 :                 if (values->IsNull(nIdxStart + k))
    1917         356 :                     aosList.AddString(
    1918             :                         "");  // we cannot have null strings in a list
    1919             :                 else
    1920        5347 :                     aosList.AddString(values->GetString(nIdxStart + k).c_str());
    1921             :             }
    1922        3552 :             poFeature->SetField(i, aosList.List());
    1923        3552 :             break;
    1924             :         }
    1925             : 
    1926        1491 :         case arrow::Type::LARGE_STRING:
    1927             :         {
    1928        2982 :             const auto values =
    1929             :                 std::static_pointer_cast<arrow::LargeStringArray>(
    1930             :                     array->values());
    1931        1491 :             const auto nIdxStart = array->value_offset(nIdxInArray);
    1932        1491 :             const auto nCount = GetListLength(array, nIdxInArray);
    1933        2982 :             CPLStringList aosList;
    1934        3251 :             for (auto k = decltype(nCount){0}; k < nCount; k++)
    1935             :             {
    1936        1760 :                 if (values->IsNull(nIdxStart + k))
    1937         356 :                     aosList.AddString(
    1938             :                         "");  // we cannot have null strings in a list
    1939             :                 else
    1940        1404 :                     aosList.AddString(values->GetString(nIdxStart + k).c_str());
    1941             :             }
    1942        1491 :             poFeature->SetField(i, aosList.List());
    1943        1491 :             break;
    1944             :         }
    1945             : 
    1946             : #if ARROW_VERSION_MAJOR >= 15
    1947           2 :         case arrow::Type::STRING_VIEW:
    1948             :         {
    1949           4 :             const auto values =
    1950             :                 std::static_pointer_cast<arrow::StringViewArray>(
    1951             :                     array->values());
    1952           2 :             const auto nIdxStart = array->value_offset(nIdxInArray);
    1953           2 :             const int nCount = GetListLength(array, nIdxInArray);
    1954           4 :             CPLStringList aosList;
    1955           6 :             for (int k = 0; k < nCount; k++)
    1956             :             {
    1957           4 :                 if (values->IsNull(nIdxStart + k))
    1958           1 :                     aosList.AddString(
    1959             :                         "");  // we cannot have null strings in a list
    1960             :                 else
    1961           3 :                     aosList.AddString(values->GetString(nIdxStart + k).c_str());
    1962             :             }
    1963           2 :             poFeature->SetField(i, aosList.List());
    1964           2 :             break;
    1965             :         }
    1966             : #endif
    1967             : 
    1968           2 :         case arrow::Type::BINARY:
    1969             :         {
    1970           4 :             const auto values =
    1971             :                 std::static_pointer_cast<arrow::BinaryArray>(array->values());
    1972           2 :             const auto nIdxStart = array->value_offset(nIdxInArray);
    1973           2 :             const auto nCount = GetListLength(array, nIdxInArray);
    1974           4 :             CPLStringList aosList;
    1975           6 :             for (auto k = decltype(nCount){0}; k < nCount; k++)
    1976             :             {
    1977           4 :                 if (values->IsNull(nIdxStart + k))
    1978             :                 {
    1979           1 :                     aosList.AddString(
    1980             :                         "");  // we cannot have null strings in a list
    1981             :                 }
    1982             :                 else
    1983             :                 {
    1984           3 :                     int length = 0;
    1985           3 :                     const uint8_t *data =
    1986           3 :                         values->GetValue(nIdxStart + k, &length);
    1987           3 :                     bool bIsASCII = true;
    1988          10 :                     for (int j = 0; j < length && bIsASCII; ++j)
    1989           7 :                         bIsASCII = data[j] >= 32 && data[j] <= 127;
    1990           3 :                     if (bIsASCII)
    1991             :                     {
    1992           2 :                         aosList.AddString(
    1993             :                             std::string(reinterpret_cast<const char *>(data),
    1994             :                                         length)
    1995             :                                 .c_str());
    1996             :                     }
    1997             :                     else
    1998             :                     {
    1999           1 :                         char *pszBase64 = CPLBase64Encode(length, data);
    2000           1 :                         aosList.AddString(
    2001           2 :                             std::string("base64:").append(pszBase64).c_str());
    2002           1 :                         CPLFree(pszBase64);
    2003             :                     }
    2004             :                 }
    2005             :             }
    2006           2 :             poFeature->SetField(i, aosList.List());
    2007           2 :             break;
    2008             :         }
    2009             : 
    2010        2089 :         case arrow::Type::LIST:
    2011             :         case arrow::Type::LARGE_LIST:
    2012             :         case arrow::Type::FIXED_SIZE_LIST:
    2013             :         case arrow::Type::MAP:
    2014             :         case arrow::Type::STRUCT:
    2015             :         {
    2016        2089 :             poFeature->SetField(
    2017             :                 i, GetListAsJSON(array, static_cast<size_t>(nIdxInArray))
    2018             :                        .Format(CPLJSONObject::PrettyFormat::Plain)
    2019             :                        .c_str());
    2020        2089 :             break;
    2021             :         }
    2022             : 
    2023           0 :         default:
    2024             :         {
    2025           0 :             CPLDebug("ARROW", "ReadList(): unexpected data type %s",
    2026           0 :                      array->values()->type()->ToString().c_str());
    2027           0 :             break;
    2028             :         }
    2029             :     }
    2030       50304 : }
    2031             : 
    2032             : /************************************************************************/
    2033             : /*                          SetPointsOfLine()                           */
    2034             : /************************************************************************/
    2035             : 
    2036             : template <bool bHasZ, bool bHasM, int nDim>
    2037         960 : void SetPointsOfLine(OGRLineString *poLS, const arrow::DoubleArray *pointValues,
    2038             :                      size_t pointOffset, int numPoints)
    2039             : {
    2040             :     if (!bHasZ && !bHasM)
    2041             :     {
    2042             :         static_assert(sizeof(OGRRawPoint) == 2 * sizeof(double),
    2043             :                       "sizeof(OGRRawPoint) == 2 * sizeof(double)");
    2044        1240 :         poLS->setPoints(numPoints,
    2045             :                         reinterpret_cast<const OGRRawPoint *>(
    2046         620 :                             pointValues->raw_values() + pointOffset));
    2047         620 :         return;
    2048             :     }
    2049             : 
    2050         340 :     poLS->setNumPoints(numPoints, FALSE);
    2051        1634 :     for (int k = 0; k < numPoints; k++)
    2052             :     {
    2053             :         if constexpr (bHasZ)
    2054             :         {
    2055             :             if constexpr (bHasM)
    2056             :             {
    2057         330 :                 poLS->setPoint(k, pointValues->Value(pointOffset + nDim * k),
    2058         330 :                                pointValues->Value(pointOffset + nDim * k + 1),
    2059         330 :                                pointValues->Value(pointOffset + nDim * k + 2),
    2060         330 :                                pointValues->Value(pointOffset + nDim * k + 3));
    2061             :             }
    2062             :             else
    2063             :             {
    2064         634 :                 poLS->setPoint(k, pointValues->Value(pointOffset + nDim * k),
    2065         634 :                                pointValues->Value(pointOffset + nDim * k + 1),
    2066         634 :                                pointValues->Value(pointOffset + nDim * k + 2));
    2067             :             }
    2068             :         }
    2069             :         else /* if( bHasM ) */
    2070             :         {
    2071         330 :             poLS->setPointM(k, pointValues->Value(pointOffset + nDim * k),
    2072         330 :                             pointValues->Value(pointOffset + nDim * k + 1),
    2073         330 :                             pointValues->Value(pointOffset + nDim * k + 2));
    2074             :         }
    2075             :     }
    2076             : }
    2077             : 
    2078             : typedef void (*SetPointsOfLineType)(OGRLineString *, const arrow::DoubleArray *,
    2079             :                                     size_t, int);
    2080             : 
    2081         836 : static SetPointsOfLineType GetSetPointsOfLine(bool bHasZ, bool bHasM)
    2082             : {
    2083         836 :     if (bHasZ && bHasM)
    2084          66 :         return SetPointsOfLine<true, true, 4>;
    2085         770 :     if (bHasZ)
    2086         168 :         return SetPointsOfLine<true, false, 3>;
    2087         602 :     if (bHasM)
    2088          66 :         return SetPointsOfLine<false, true, 3>;
    2089         536 :     return SetPointsOfLine<false, false, 2>;
    2090             : }
    2091             : 
    2092             : /************************************************************************/
    2093             : /*                       SetPointsOfLineStruct()                        */
    2094             : /************************************************************************/
    2095             : 
    2096             : template <bool bHasZ, bool bHasM, int nDim>
    2097        2116 : void SetPointsOfLineStruct(OGRLineString *poLS,
    2098             :                            const arrow::StructArray *structArray,
    2099             :                            size_t pointOffset, int numPoints)
    2100             : {
    2101        2116 :     CPLAssert(structArray->num_fields() == nDim);
    2102        2116 :     const auto &fields = structArray->fields();
    2103        2116 :     const auto &fieldX = fields[0];
    2104        2116 :     CPLAssert(fieldX->type_id() == arrow::Type::DOUBLE);
    2105        2116 :     const auto fieldXDouble = static_cast<arrow::DoubleArray *>(fieldX.get());
    2106        2116 :     const auto &fieldY = fields[1];
    2107        2116 :     CPLAssert(fieldY->type_id() == arrow::Type::DOUBLE);
    2108        2116 :     const auto fieldYDouble = static_cast<arrow::DoubleArray *>(fieldY.get());
    2109        2116 :     const arrow::DoubleArray *fieldZDouble = nullptr;
    2110        2116 :     const arrow::DoubleArray *fieldMDouble = nullptr;
    2111        2116 :     int iField = 2;
    2112             :     if constexpr (bHasZ)
    2113             :     {
    2114         686 :         const auto &field = fields[iField];
    2115         686 :         ++iField;
    2116         686 :         CPLAssert(field->type_id() == arrow::Type::DOUBLE);
    2117         686 :         fieldZDouble = static_cast<arrow::DoubleArray *>(field.get());
    2118             :     }
    2119             :     if constexpr (bHasM)
    2120             :     {
    2121          26 :         const auto &field = fields[iField];
    2122          26 :         CPLAssert(field->type_id() == arrow::Type::DOUBLE);
    2123          26 :         fieldMDouble = static_cast<arrow::DoubleArray *>(field.get());
    2124             :     }
    2125             : 
    2126        2116 :     poLS->setNumPoints(numPoints, FALSE);
    2127        9680 :     for (int k = 0; k < numPoints; k++)
    2128             :     {
    2129             :         if constexpr (bHasZ)
    2130             :         {
    2131             :             if constexpr (bHasM)
    2132             :             {
    2133          55 :                 poLS->setPoint(k, fieldXDouble->Value(pointOffset + k),
    2134          55 :                                fieldYDouble->Value(pointOffset + k),
    2135          55 :                                fieldZDouble->Value(pointOffset + k),
    2136          55 :                                fieldMDouble->Value(pointOffset + k));
    2137             :             }
    2138             :             else
    2139             :             {
    2140        1975 :                 poLS->setPoint(k, fieldXDouble->Value(pointOffset + k),
    2141        1975 :                                fieldYDouble->Value(pointOffset + k),
    2142        1975 :                                fieldZDouble->Value(pointOffset + k));
    2143             :             }
    2144             :         }
    2145             :         else if constexpr (bHasM)
    2146             :         {
    2147          55 :             poLS->setPointM(k, fieldXDouble->Value(pointOffset + k),
    2148          55 :                             fieldYDouble->Value(pointOffset + k),
    2149          55 :                             fieldMDouble->Value(pointOffset + k));
    2150             :         }
    2151             :         else
    2152             :         {
    2153        5479 :             poLS->setPoint(k, fieldXDouble->Value(pointOffset + k),
    2154        5479 :                            fieldYDouble->Value(pointOffset + k));
    2155             :         }
    2156             :     }
    2157        2116 : }
    2158             : 
    2159             : typedef void (*SetPointsOfLineStructType)(OGRLineString *,
    2160             :                                           const arrow::StructArray *, size_t,
    2161             :                                           int);
    2162             : 
    2163        1628 : static SetPointsOfLineStructType GetSetPointsOfLineStruct(bool bHasZ,
    2164             :                                                           bool bHasM)
    2165             : {
    2166        1628 :     if (bHasZ && bHasM)
    2167          11 :         return SetPointsOfLineStruct<true, true, 4>;
    2168        1617 :     if (bHasZ)
    2169         623 :         return SetPointsOfLineStruct<true, false, 3>;
    2170         994 :     if (bHasM)
    2171          11 :         return SetPointsOfLineStruct<false, true, 3>;
    2172         983 :     return SetPointsOfLineStruct<false, false, 2>;
    2173             : }
    2174             : 
    2175             : /************************************************************************/
    2176             : /*                           TimestampToOGR()                           */
    2177             : /************************************************************************/
    2178             : 
    2179             : inline void
    2180       11300 : OGRArrowLayer::TimestampToOGR(int64_t timestamp,
    2181             :                               const arrow::TimestampType *timestampType,
    2182             :                               int nTZFlag, OGRField *psField)
    2183             : {
    2184       11300 :     const auto unit = timestampType->unit();
    2185       11300 :     double floatingPart = 0;
    2186       11300 :     if (unit == arrow::TimeUnit::MILLI)
    2187             :     {
    2188        7478 :         floatingPart = (timestamp % 1000) / 1e3;
    2189        7478 :         timestamp /= 1000;
    2190             :     }
    2191        3822 :     else if (unit == arrow::TimeUnit::MICRO)
    2192             :     {
    2193        3330 :         floatingPart = (timestamp % (1000 * 1000)) / 1e6;
    2194        3330 :         timestamp /= 1000 * 1000;
    2195             :     }
    2196         492 :     else if (unit == arrow::TimeUnit::NANO)
    2197             :     {
    2198         246 :         floatingPart = (timestamp % (1000 * 1000 * 1000)) / 1e9;
    2199         246 :         timestamp /= 1000 * 1000 * 1000;
    2200             :     }
    2201       11300 :     if (nTZFlag > OGR_TZFLAG_MIXED_TZ)
    2202             :     {
    2203        5660 :         const int TZOffset = (nTZFlag - OGR_TZFLAG_UTC) * 15;
    2204        5660 :         timestamp += TZOffset * 60;
    2205             :     }
    2206             :     struct tm dt;
    2207       11300 :     CPLUnixTimeToYMDHMS(timestamp, &dt);
    2208       11300 :     psField->Date.Year = static_cast<GInt16>(dt.tm_year + 1900);
    2209       11300 :     psField->Date.Month = static_cast<GByte>(dt.tm_mon + 1);
    2210       11300 :     psField->Date.Day = static_cast<GByte>(dt.tm_mday);
    2211       11300 :     psField->Date.Hour = static_cast<GByte>(dt.tm_hour);
    2212       11300 :     psField->Date.Minute = static_cast<GByte>(dt.tm_min);
    2213       11300 :     psField->Date.TZFlag = static_cast<GByte>(nTZFlag);
    2214       11300 :     psField->Date.Second = static_cast<float>(dt.tm_sec + floatingPart);
    2215       11300 : }
    2216             : 
    2217             : /************************************************************************/
    2218             : /*                            ReadFeature()                             */
    2219             : /************************************************************************/
    2220             : 
    2221       10899 : inline OGRFeature *OGRArrowLayer::ReadFeature(
    2222             :     int64_t nIdxInBatch,
    2223             :     const std::vector<std::shared_ptr<arrow::Array>> &poColumnArrays) const
    2224             : {
    2225       10899 :     OGRFeature *poFeature = new OGRFeature(m_poFeatureDefn);
    2226             : 
    2227       10899 :     if (m_iFIDArrowColumn >= 0)
    2228             :     {
    2229        3400 :         const int iCol =
    2230        3400 :             m_bIgnoredFields ? m_nRequestedFIDColumn : m_iFIDArrowColumn;
    2231        3400 :         const arrow::Array *array = poColumnArrays[iCol].get();
    2232        3400 :         if (!array->IsNull(nIdxInBatch))
    2233             :         {
    2234        3400 :             if (array->type_id() == arrow::Type::INT64)
    2235             :             {
    2236        3400 :                 const auto castArray =
    2237             :                     static_cast<const arrow::Int64Array *>(array);
    2238        3400 :                 poFeature->SetFID(
    2239        3400 :                     static_cast<GIntBig>(castArray->Value(nIdxInBatch)));
    2240             :             }
    2241           0 :             else if (array->type_id() == arrow::Type::INT32)
    2242             :             {
    2243           0 :                 const auto castArray =
    2244             :                     static_cast<const arrow::Int32Array *>(array);
    2245           0 :                 poFeature->SetFID(castArray->Value(nIdxInBatch));
    2246             :             }
    2247             :         }
    2248             :     }
    2249             : 
    2250             :     const auto ReadList =
    2251      151416 :         [this, poFeature, nIdxInBatch](const auto *array, int iField)
    2252             :     {
    2253             :         const auto listType =
    2254       50472 :             static_cast<const arrow::ListType *>(array->data()->type.get());
    2255             : 
    2256       50472 :         if (m_bListsAsStringJson)
    2257             :         {
    2258         168 :             poFeature->SetField(
    2259             :                 iField, GetListAsJSON(array, static_cast<size_t>(nIdxInBatch))
    2260             :                             .Format(CPLJSONObject::PrettyFormat::Plain)
    2261             :                             .c_str());
    2262             :         }
    2263             :         else
    2264             :         {
    2265       50304 :             ::ReadList(poFeature, iField, nIdxInBatch, array,
    2266       50304 :                        listType->value_field()->type()->id());
    2267             :         }
    2268       61371 :     };
    2269             : 
    2270       10899 :     const int nFieldCount = m_poFeatureDefn->GetFieldCount();
    2271      179878 :     for (int i = 0; i < nFieldCount; ++i)
    2272             :     {
    2273             :         int iCol;
    2274      168979 :         if (m_bIgnoredFields)
    2275             :         {
    2276       17032 :             iCol = m_anMapFieldIndexToArrayIndex[i];
    2277       17032 :             if (iCol < 0)
    2278        5434 :                 continue;
    2279             :         }
    2280             :         else
    2281             :         {
    2282      151947 :             iCol = m_anMapFieldIndexToArrowColumn[i][0];
    2283             :         }
    2284             : 
    2285      163545 :         const arrow::Array *array = GetStorageArray(poColumnArrays[iCol].get());
    2286      163545 :         if (array->IsNull(nIdxInBatch))
    2287             :         {
    2288       17440 :             poFeature->SetFieldNull(i);
    2289       17440 :             continue;
    2290             :         }
    2291             : 
    2292      146105 :         int j = 1;
    2293      146105 :         bool bSkipToNextField = false;
    2294      156898 :         if (array->type_id() == arrow::Type::STRUCT &&
    2295      156898 :             m_poFeatureDefn->GetFieldDefn(i)->GetType() == OFTDateTime &&
    2296          19 :             m_poFeatureDefn->GetFieldDefn(i)->GetTZFlag() ==
    2297             :                 OGR_TZFLAG_MIXED_TZ)
    2298             :         {
    2299          19 :             const auto structArray =
    2300             :                 static_cast<const arrow::StructArray *>(array);
    2301          19 :             const auto &subArrays = structArray->fields();
    2302          19 :             CPLAssert(subArrays.size() == 2);
    2303             :             const auto timestampType = static_cast<arrow::TimestampType *>(
    2304          19 :                 subArrays[0]->data()->type.get());
    2305             :             const auto timestampArray =
    2306          19 :                 static_cast<const arrow::TimestampArray *>(subArrays[0].get());
    2307          19 :             const int64_t timestamp = timestampArray->Value(nIdxInBatch);
    2308             :             const auto offsetMinutesArray =
    2309          19 :                 static_cast<const arrow::Int16Array *>(subArrays[1].get());
    2310          19 :             const int nOffsetMinutes = offsetMinutesArray->Value(nIdxInBatch);
    2311          19 :             const int MAX_TIME_ZONE_HOUR = 14;
    2312          19 :             const int nTZFlag =
    2313          17 :                 nOffsetMinutes >= -MAX_TIME_ZONE_HOUR * 60 &&
    2314             :                         nOffsetMinutes <= MAX_TIME_ZONE_HOUR * 60
    2315          36 :                     ? OGR_TZFLAG_UTC + nOffsetMinutes / 15
    2316             :                     : OGR_TZFLAG_UTC;
    2317             :             OGRField sField;
    2318          19 :             sField.Set.nMarker1 = OGRUnsetMarker;
    2319          19 :             sField.Set.nMarker2 = OGRUnsetMarker;
    2320          19 :             sField.Set.nMarker3 = OGRUnsetMarker;
    2321          19 :             TimestampToOGR(timestamp, timestampType, nTZFlag, &sField);
    2322          19 :             poFeature->SetField(i, &sField);
    2323          19 :             continue;
    2324             :         }
    2325             :         else
    2326             :         {
    2327      160491 :             while (array->type_id() == arrow::Type::STRUCT)
    2328             :             {
    2329       14416 :                 const auto castArray =
    2330             :                     static_cast<const arrow::StructArray *>(array);
    2331       14416 :                 const auto &subArrays = castArray->fields();
    2332       14416 :                 CPLAssert(j < static_cast<int>(
    2333             :                                   m_anMapFieldIndexToArrowColumn[i].size()));
    2334       14416 :                 const int iArrowSubcol = m_anMapFieldIndexToArrowColumn[i][j];
    2335       14416 :                 j++;
    2336       14416 :                 CPLAssert(iArrowSubcol < static_cast<int>(subArrays.size()));
    2337       14416 :                 array = GetStorageArray(subArrays[iArrowSubcol].get());
    2338       14416 :                 if (array->IsNull(nIdxInBatch))
    2339             :                 {
    2340          11 :                     poFeature->SetFieldNull(i);
    2341          11 :                     bSkipToNextField = true;
    2342          11 :                     break;
    2343             :                 }
    2344             :             }
    2345             :         }
    2346      146086 :         if (bSkipToNextField)
    2347          11 :             continue;
    2348             : 
    2349      146075 :         if (array->type_id() == arrow::Type::DICTIONARY)
    2350             :         {
    2351        1516 :             const auto castArray =
    2352             :                 static_cast<const arrow::DictionaryArray *>(array);
    2353             :             m_poReadFeatureTmpArray =
    2354        1516 :                 castArray->indices();  // does not return a const reference
    2355        1516 :             array = GetStorageArray(m_poReadFeatureTmpArray.get());
    2356        1516 :             if (array->IsNull(nIdxInBatch))
    2357             :             {
    2358           0 :                 poFeature->SetFieldNull(i);
    2359           0 :                 continue;
    2360             :             }
    2361             :         }
    2362             : 
    2363      146075 :         switch (array->type_id())
    2364             :         {
    2365           0 :             case arrow::Type::NA:
    2366           0 :                 break;
    2367             : 
    2368        1565 :             case arrow::Type::BOOL:
    2369             :             {
    2370        1565 :                 const auto castArray =
    2371             :                     static_cast<const arrow::BooleanArray *>(array);
    2372        1565 :                 poFeature->SetFieldSameTypeUnsafe(
    2373        1565 :                     i, castArray->Value(nIdxInBatch));
    2374        1565 :                 break;
    2375             :             }
    2376        1500 :             case arrow::Type::UINT8:
    2377             :             {
    2378        1500 :                 const auto castArray =
    2379             :                     static_cast<const arrow::UInt8Array *>(array);
    2380        1500 :                 poFeature->SetFieldSameTypeUnsafe(
    2381        1500 :                     i, castArray->Value(nIdxInBatch));
    2382        1500 :                 break;
    2383             :             }
    2384        1496 :             case arrow::Type::INT8:
    2385             :             {
    2386        1496 :                 const auto castArray =
    2387             :                     static_cast<const arrow::Int8Array *>(array);
    2388        1496 :                 poFeature->SetFieldSameTypeUnsafe(
    2389        1496 :                     i, castArray->Value(nIdxInBatch));
    2390        1496 :                 break;
    2391             :             }
    2392        1496 :             case arrow::Type::UINT16:
    2393             :             {
    2394        1496 :                 const auto castArray =
    2395             :                     static_cast<const arrow::UInt16Array *>(array);
    2396        1496 :                 poFeature->SetFieldSameTypeUnsafe(
    2397        1496 :                     i, castArray->Value(nIdxInBatch));
    2398        1496 :                 break;
    2399             :             }
    2400        1574 :             case arrow::Type::INT16:
    2401             :             {
    2402        1574 :                 const auto castArray =
    2403             :                     static_cast<const arrow::Int16Array *>(array);
    2404        1574 :                 poFeature->SetFieldSameTypeUnsafe(
    2405        1574 :                     i, castArray->Value(nIdxInBatch));
    2406        1574 :                 break;
    2407             :             }
    2408         203 :             case arrow::Type::UINT32:
    2409             :             {
    2410         203 :                 const auto castArray =
    2411             :                     static_cast<const arrow::UInt32Array *>(array);
    2412         203 :                 poFeature->SetFieldSameTypeUnsafe(
    2413         203 :                     i, static_cast<GIntBig>(castArray->Value(nIdxInBatch)));
    2414         203 :                 break;
    2415             :             }
    2416        8565 :             case arrow::Type::INT32:
    2417             :             {
    2418        8565 :                 const auto castArray =
    2419             :                     static_cast<const arrow::Int32Array *>(array);
    2420        8565 :                 poFeature->SetFieldSameTypeUnsafe(
    2421             :                     i, castArray->Value(nIdxInBatch));
    2422        8565 :                 break;
    2423             :             }
    2424        1496 :             case arrow::Type::UINT64:
    2425             :             {
    2426        1496 :                 const auto castArray =
    2427             :                     static_cast<const arrow::UInt64Array *>(array);
    2428        1496 :                 poFeature->SetFieldSameTypeUnsafe(
    2429        1496 :                     i, static_cast<double>(castArray->Value(nIdxInBatch)));
    2430        1496 :                 break;
    2431             :             }
    2432        7612 :             case arrow::Type::INT64:
    2433             :             {
    2434        7612 :                 const auto castArray =
    2435             :                     static_cast<const arrow::Int64Array *>(array);
    2436        7612 :                 poFeature->SetFieldSameTypeUnsafe(
    2437        7612 :                     i, static_cast<GIntBig>(castArray->Value(nIdxInBatch)));
    2438        7612 :                 break;
    2439             :             }
    2440         203 :             case arrow::Type::HALF_FLOAT:
    2441             :             {
    2442         203 :                 const auto castArray =
    2443             :                     static_cast<const arrow::HalfFloatArray *>(array);
    2444         203 :                 const uint16_t nFloat16 = castArray->Value(nIdxInBatch);
    2445         203 :                 uint32_t nFloat32 = CPLHalfToFloat(nFloat16);
    2446             :                 float f;
    2447         203 :                 memcpy(&f, &nFloat32, sizeof(nFloat32));
    2448         203 :                 poFeature->SetFieldSameTypeUnsafe(i, f);
    2449         203 :                 break;
    2450             :             }
    2451        1655 :             case arrow::Type::FLOAT:
    2452             :             {
    2453        1655 :                 const auto castArray =
    2454             :                     static_cast<const arrow::FloatArray *>(array);
    2455        1655 :                 poFeature->SetFieldSameTypeUnsafe(
    2456        1655 :                     i, castArray->Value(nIdxInBatch));
    2457        1655 :                 break;
    2458             :             }
    2459        3679 :             case arrow::Type::DOUBLE:
    2460             :             {
    2461        3679 :                 const auto castArray =
    2462             :                     static_cast<const arrow::DoubleArray *>(array);
    2463        3679 :                 poFeature->SetFieldSameTypeUnsafe(
    2464             :                     i, castArray->Value(nIdxInBatch));
    2465        3679 :                 break;
    2466             :             }
    2467        7638 :             case arrow::Type::STRING:
    2468             :             {
    2469        7638 :                 const auto castArray =
    2470             :                     static_cast<const arrow::StringArray *>(array);
    2471        7638 :                 int out_length = 0;
    2472             :                 const uint8_t *data =
    2473        7638 :                     castArray->GetValue(nIdxInBatch, &out_length);
    2474             :                 char *pszString =
    2475        7638 :                     static_cast<char *>(CPLMalloc(out_length + 1));
    2476        7638 :                 memcpy(pszString, data, out_length);
    2477        7638 :                 pszString[out_length] = 0;
    2478        7638 :                 poFeature->SetFieldSameTypeUnsafe(i, pszString);
    2479        7638 :                 break;
    2480             :             }
    2481        1975 :             case arrow::Type::BINARY:
    2482             :             {
    2483        1975 :                 const auto castArray =
    2484             :                     static_cast<const arrow::BinaryArray *>(array);
    2485        1975 :                 int out_length = 0;
    2486             :                 const uint8_t *data =
    2487        1975 :                     castArray->GetValue(nIdxInBatch, &out_length);
    2488        1975 :                 poFeature->SetField(i, out_length, data);
    2489        1975 :                 break;
    2490             :             }
    2491             : #if ARROW_VERSION_MAJOR >= 15
    2492           3 :             case arrow::Type::BINARY_VIEW:
    2493             :             {
    2494           3 :                 const auto castArray =
    2495             :                     static_cast<const arrow::BinaryViewArray *>(array);
    2496           3 :                 const auto view = castArray->GetView(nIdxInBatch);
    2497           3 :                 poFeature->SetField(i, static_cast<int>(view.size()),
    2498           3 :                                     view.data());
    2499           3 :                 break;
    2500             :             }
    2501             : #endif
    2502             : #if ARROW_VERSION_MAJOR >= 15
    2503           3 :             case arrow::Type::STRING_VIEW:
    2504             :             {
    2505           3 :                 const auto castArray =
    2506             :                     static_cast<const arrow::StringViewArray *>(array);
    2507           3 :                 const auto strView = castArray->GetView(nIdxInBatch);
    2508             :                 char *pszString =
    2509           3 :                     static_cast<char *>(CPLMalloc(strView.length() + 1));
    2510           3 :                 memcpy(pszString, strView.data(), strView.length());
    2511           3 :                 pszString[strView.length()] = 0;
    2512           3 :                 poFeature->SetFieldSameTypeUnsafe(i, pszString);
    2513           3 :                 break;
    2514             :             }
    2515             : #endif
    2516        1879 :             case arrow::Type::FIXED_SIZE_BINARY:
    2517             :             {
    2518        1879 :                 const auto castArray =
    2519             :                     static_cast<const arrow::FixedSizeBinaryArray *>(array);
    2520        1879 :                 const uint8_t *data = castArray->GetValue(nIdxInBatch);
    2521        1879 :                 poFeature->SetField(i, castArray->byte_width(), data);
    2522        1879 :                 break;
    2523             :             }
    2524        3512 :             case arrow::Type::DATE32:
    2525             :             {
    2526             :                 // number of days since Epoch
    2527        3512 :                 const auto castArray =
    2528             :                     static_cast<const arrow::Date32Array *>(array);
    2529             :                 int64_t timestamp =
    2530        3512 :                     static_cast<int64_t>(castArray->Value(nIdxInBatch)) * 3600 *
    2531        3512 :                     24;
    2532             :                 struct tm dt;
    2533        3512 :                 CPLUnixTimeToYMDHMS(timestamp, &dt);
    2534        3512 :                 poFeature->SetField(i, dt.tm_year + 1900, dt.tm_mon + 1,
    2535             :                                     dt.tm_mday, 0, 0, 0);
    2536        3512 :                 break;
    2537             :             }
    2538         246 :             case arrow::Type::DATE64:
    2539             :             {
    2540             :                 // number of milliseconds since Epoch
    2541         246 :                 const auto castArray =
    2542             :                     static_cast<const arrow::Date64Array *>(array);
    2543             :                 int64_t timestamp =
    2544         246 :                     static_cast<int64_t>(castArray->Value(nIdxInBatch)) / 1000;
    2545             :                 struct tm dt;
    2546         246 :                 CPLUnixTimeToYMDHMS(timestamp, &dt);
    2547         246 :                 poFeature->SetField(i, dt.tm_year + 1900, dt.tm_mon + 1,
    2548             :                                     dt.tm_mday, 0, 0, 0);
    2549         246 :                 break;
    2550             :             }
    2551       11279 :             case arrow::Type::TIMESTAMP:
    2552             :             {
    2553             :                 const auto timestampType = static_cast<arrow::TimestampType *>(
    2554       11279 :                     array->data()->type.get());
    2555       11279 :                 const auto castArray =
    2556             :                     static_cast<const arrow::TimestampArray *>(array);
    2557       11279 :                 const int64_t timestamp = castArray->Value(nIdxInBatch);
    2558             :                 OGRField sField;
    2559       11279 :                 sField.Set.nMarker1 = OGRUnsetMarker;
    2560       11279 :                 sField.Set.nMarker2 = OGRUnsetMarker;
    2561       11279 :                 sField.Set.nMarker3 = OGRUnsetMarker;
    2562       11279 :                 TimestampToOGR(timestamp, timestampType,
    2563       11279 :                                m_poFeatureDefn->GetFieldDefn(i)->GetTZFlag(),
    2564             :                                &sField);
    2565       11279 :                 poFeature->SetField(i, &sField);
    2566       11279 :                 break;
    2567             :             }
    2568        3383 :             case arrow::Type::TIME32:
    2569             :             {
    2570             :                 const auto timestampType =
    2571        3383 :                     static_cast<arrow::Time32Type *>(array->data()->type.get());
    2572        3383 :                 const auto castArray =
    2573             :                     static_cast<const arrow::Time32Array *>(array);
    2574        3383 :                 const auto unit = timestampType->unit();
    2575        3383 :                 int value = castArray->Value(nIdxInBatch);
    2576        3383 :                 double floatingPart = 0;
    2577        3383 :                 if (unit == arrow::TimeUnit::MILLI)
    2578             :                 {
    2579        3178 :                     floatingPart = (value % 1000) / 1e3;
    2580        3178 :                     value /= 1000;
    2581             :                 }
    2582        3383 :                 const int nHour = value / 3600;
    2583        3383 :                 const int nMinute = (value / 60) % 60;
    2584        3383 :                 const int nSecond = value % 60;
    2585        3383 :                 poFeature->SetField(i, 0, 0, 0, nHour, nMinute,
    2586        3383 :                                     static_cast<float>(nSecond + floatingPart));
    2587        3383 :                 break;
    2588             :             }
    2589        3087 :             case arrow::Type::TIME64:
    2590             :             {
    2591        3087 :                 const auto castArray =
    2592             :                     static_cast<const arrow::Time64Array *>(array);
    2593        3087 :                 poFeature->SetField(
    2594        3087 :                     i, static_cast<GIntBig>(castArray->Value(nIdxInBatch)));
    2595        3087 :                 break;
    2596             :             }
    2597             : 
    2598             : #if ARROW_VERSION_MAJOR >= 18
    2599           0 :             case arrow::Type::DECIMAL32:
    2600             :             {
    2601           0 :                 const auto castArray =
    2602             :                     static_cast<const arrow::Decimal32Array *>(array);
    2603           0 :                 poFeature->SetField(
    2604           0 :                     i, CPLAtof(castArray->FormatValue(nIdxInBatch).c_str()));
    2605           0 :                 break;
    2606             :             }
    2607             : 
    2608           0 :             case arrow::Type::DECIMAL64:
    2609             :             {
    2610           0 :                 const auto castArray =
    2611             :                     static_cast<const arrow::Decimal64Array *>(array);
    2612           0 :                 poFeature->SetField(
    2613           0 :                     i, CPLAtof(castArray->FormatValue(nIdxInBatch).c_str()));
    2614           0 :                 break;
    2615             :             }
    2616             : #endif
    2617             : 
    2618        1575 :             case arrow::Type::DECIMAL128:
    2619             :             {
    2620        1575 :                 const auto castArray =
    2621             :                     static_cast<const arrow::Decimal128Array *>(array);
    2622        1575 :                 poFeature->SetField(
    2623        3150 :                     i, CPLAtof(castArray->FormatValue(nIdxInBatch).c_str()));
    2624        1575 :                 break;
    2625             :             }
    2626             : 
    2627        1391 :             case arrow::Type::DECIMAL256:
    2628             :             {
    2629        1391 :                 const auto castArray =
    2630             :                     static_cast<const arrow::Decimal256Array *>(array);
    2631        1391 :                 poFeature->SetField(
    2632        2782 :                     i, CPLAtof(castArray->FormatValue(nIdxInBatch).c_str()));
    2633        1391 :                 break;
    2634             :             }
    2635             : 
    2636       28993 :             case arrow::Type::LIST:
    2637             :             {
    2638       28993 :                 ReadList(static_cast<const arrow::ListArray *>(array), i);
    2639       28993 :                 break;
    2640             :             }
    2641             : 
    2642           2 :             case arrow::Type::LARGE_LIST:
    2643             :             {
    2644           2 :                 ReadList(static_cast<const arrow::LargeListArray *>(array), i);
    2645           2 :                 break;
    2646             :             }
    2647             : 
    2648       21477 :             case arrow::Type::FIXED_SIZE_LIST:
    2649             :             {
    2650       21477 :                 ReadList(static_cast<const arrow::FixedSizeListArray *>(array),
    2651             :                          i);
    2652       21477 :                 break;
    2653             :             }
    2654             : 
    2655        1496 :             case arrow::Type::LARGE_STRING:
    2656             :             {
    2657        1496 :                 const auto castArray =
    2658             :                     static_cast<const arrow::LargeStringArray *>(array);
    2659        1496 :                 poFeature->SetField(i,
    2660        2992 :                                     castArray->GetString(nIdxInBatch).c_str());
    2661        1496 :                 break;
    2662             :             }
    2663        1787 :             case arrow::Type::LARGE_BINARY:
    2664             :             {
    2665        1787 :                 const auto castArray =
    2666             :                     static_cast<const arrow::LargeBinaryArray *>(array);
    2667        1787 :                 arrow::LargeBinaryArray::offset_type out_length = 0;
    2668             :                 const uint8_t *data =
    2669        1787 :                     castArray->GetValue(nIdxInBatch, &out_length);
    2670        1787 :                 if (out_length >= 0 && out_length <= INT_MAX - 1)
    2671             :                 {
    2672             :                     // coverity[overflow_sink]
    2673        1787 :                     poFeature->SetField(i, static_cast<int>(out_length), data);
    2674             :                 }
    2675             :                 else
    2676             :                 {
    2677             :                     // this is probably the most likely code path if people use
    2678             :                     // LargeBinary...
    2679           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    2680             :                              "Too large binary: " CPL_FRMT_GUIB " bytes",
    2681             :                              static_cast<GUIntBig>(out_length));
    2682             :                 }
    2683        1787 :                 break;
    2684             :             }
    2685             : 
    2686       25305 :             case arrow::Type::MAP:
    2687             :             {
    2688       25305 :                 const auto castArray =
    2689             :                     static_cast<const arrow::MapArray *>(array);
    2690       25305 :                 poFeature->SetField(
    2691       25305 :                     i, GetMapAsJSON(castArray, static_cast<size_t>(nIdxInBatch))
    2692       50610 :                            .Format(CPLJSONObject::PrettyFormat::Plain)
    2693             :                            .c_str());
    2694       25305 :                 break;
    2695             :             }
    2696             : 
    2697             :             // unhandled types
    2698           0 :             case arrow::Type::STRUCT:  // should not happen
    2699             :             case arrow::Type::INTERVAL_MONTHS:
    2700             :             case arrow::Type::INTERVAL_DAY_TIME:
    2701             :             case arrow::Type::SPARSE_UNION:
    2702             :             case arrow::Type::DENSE_UNION:
    2703             :             case arrow::Type::DICTIONARY:
    2704             :             case arrow::Type::EXTENSION:
    2705             :             case arrow::Type::DURATION:
    2706             :             case arrow::Type::INTERVAL_MONTH_DAY_NANO:
    2707             : #if ARROW_VERSION_MAJOR >= 12
    2708             :             case arrow::Type::RUN_END_ENCODED:
    2709             : #endif
    2710             : #if ARROW_VERSION_MAJOR >= 15
    2711             :             case arrow::Type::LIST_VIEW:
    2712             :             case arrow::Type::LARGE_LIST_VIEW:
    2713             : #endif
    2714             :             case arrow::Type::MAX_ID:
    2715             :             {
    2716             :                 // Shouldn't happen normally as we should have discarded those
    2717             :                 // fields when creating OGR field definitions
    2718           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2719             :                          "Cannot read content for field %s",
    2720           0 :                          m_poFeatureDefn->GetFieldDefn(i)->GetNameRef());
    2721           0 :                 break;
    2722             :             }
    2723             :         }
    2724             :     }
    2725             : 
    2726       10899 :     const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount();
    2727       21446 :     for (int i = 0; i < nGeomFieldCount; ++i)
    2728             :     {
    2729             :         int iCol;
    2730       10547 :         if (m_bIgnoredFields)
    2731             :         {
    2732        1513 :             iCol = m_anMapGeomFieldIndexToArrayIndex[i];
    2733        1513 :             if (iCol < 0)
    2734          54 :                 continue;
    2735             :         }
    2736             :         else
    2737             :         {
    2738        9034 :             iCol = m_anMapGeomFieldIndexToArrowColumn[i];
    2739             :         }
    2740             : 
    2741       10493 :         const auto array = GetStorageArray(poColumnArrays[iCol].get());
    2742       10493 :         auto poGeometry = ReadGeometry(i, array, nIdxInBatch);
    2743       10493 :         if (poGeometry)
    2744             :         {
    2745        7791 :             const auto poGeomFieldDefn = m_poFeatureDefn->GetGeomFieldDefn(i);
    2746        8288 :             if (wkbFlatten(poGeometry->getGeometryType()) == wkbLineString &&
    2747         497 :                 wkbFlatten(poGeomFieldDefn->GetType()) == wkbMultiLineString)
    2748             :             {
    2749             :                 poGeometry =
    2750           3 :                     OGRGeometryFactory::forceToMultiLineString(poGeometry);
    2751             :             }
    2752        8840 :             else if (wkbFlatten(poGeometry->getGeometryType()) == wkbPolygon &&
    2753        1052 :                      wkbFlatten(poGeomFieldDefn->GetType()) == wkbMultiPolygon)
    2754             :             {
    2755             :                 poGeometry =
    2756          33 :                     OGRGeometryFactory::forceToMultiPolygon(poGeometry);
    2757             :             }
    2758        7791 :             if (OGR_GT_HasZ(poGeomFieldDefn->GetType()) && !poGeometry->Is3D())
    2759             :             {
    2760          63 :                 poGeometry->set3D(true);
    2761             :             }
    2762        7791 :             poFeature->SetGeomFieldDirectly(i, poGeometry);
    2763             :         }
    2764             :     }
    2765             : 
    2766       10899 :     return poFeature;
    2767             : }
    2768             : 
    2769             : /************************************************************************/
    2770             : /*                            ReadGeometry()                            */
    2771             : /************************************************************************/
    2772             : 
    2773       10713 : inline OGRGeometry *OGRArrowLayer::ReadGeometry(int iGeomField,
    2774             :                                                 const arrow::Array *array,
    2775             :                                                 int64_t nIdxInBatch) const
    2776             : {
    2777       10713 :     if (array->IsNull(nIdxInBatch))
    2778             :     {
    2779        2751 :         return nullptr;
    2780             :     }
    2781        7962 :     OGRGeometry *poGeometry = nullptr;
    2782        7962 :     const auto poGeomFieldDefn = m_poFeatureDefn->GetGeomFieldDefn(iGeomField);
    2783        7962 :     const auto eGeomType = poGeomFieldDefn->GetType();
    2784        7962 :     const bool bHasZ = CPL_TO_BOOL(OGR_GT_HasZ(eGeomType));
    2785        7962 :     const bool bHasM = CPL_TO_BOOL(OGR_GT_HasM(eGeomType));
    2786        7962 :     const int nDim = 2 + (bHasZ ? 1 : 0) + (bHasM ? 1 : 0);
    2787             : 
    2788             :     const auto CreatePoint =
    2789         798 :         [bHasZ, bHasM](const arrow::DoubleArray *pointValues, int pointOffset)
    2790             :     {
    2791         399 :         if (bHasZ)
    2792             :         {
    2793         199 :             if (bHasM)
    2794             :             {
    2795          66 :                 return new OGRPoint(pointValues->Value(pointOffset),
    2796          66 :                                     pointValues->Value(pointOffset + 1),
    2797          66 :                                     pointValues->Value(pointOffset + 2),
    2798          66 :                                     pointValues->Value(pointOffset + 3));
    2799             :             }
    2800             :             else
    2801             :             {
    2802         133 :                 return new OGRPoint(pointValues->Value(pointOffset),
    2803         133 :                                     pointValues->Value(pointOffset + 1),
    2804         133 :                                     pointValues->Value(pointOffset + 2));
    2805             :             }
    2806             :         }
    2807         200 :         else if (bHasM)
    2808             :         {
    2809          66 :             return OGRPoint::createXYM(pointValues->Value(pointOffset),
    2810          66 :                                        pointValues->Value(pointOffset + 1),
    2811         132 :                                        pointValues->Value(pointOffset + 2));
    2812             :         }
    2813             :         else
    2814             :         {
    2815         134 :             return new OGRPoint(pointValues->Value(pointOffset),
    2816         134 :                                 pointValues->Value(pointOffset + 1));
    2817             :         }
    2818        7962 :     };
    2819             : 
    2820             :     const auto CreateStructPoint =
    2821         923 :         [nDim, bHasZ, bHasM](const arrow::StructArray *structArray,
    2822        2769 :                              int64_t pointOffset)
    2823             :     {
    2824         923 :         CPL_IGNORE_RET_VAL(nDim);
    2825         923 :         CPLAssert(structArray->num_fields() == nDim);
    2826         923 :         const auto &fieldX = structArray->field(0);
    2827         923 :         CPLAssert(fieldX->type_id() == arrow::Type::DOUBLE);
    2828             :         const auto fieldXDouble =
    2829         923 :             static_cast<arrow::DoubleArray *>(fieldX.get());
    2830         923 :         const auto &fieldY = structArray->field(1);
    2831         923 :         CPLAssert(fieldY->type_id() == arrow::Type::DOUBLE);
    2832             :         const auto fieldYDouble =
    2833         923 :             static_cast<arrow::DoubleArray *>(fieldY.get());
    2834         923 :         if (bHasZ)
    2835             :         {
    2836         406 :             const auto &fieldZ = structArray->field(2);
    2837         406 :             CPLAssert(fieldZ->type_id() == arrow::Type::DOUBLE);
    2838             :             const auto fieldZDouble =
    2839         406 :                 static_cast<arrow::DoubleArray *>(fieldZ.get());
    2840         406 :             if (bHasM)
    2841             :             {
    2842          11 :                 const auto &fieldM = structArray->field(3);
    2843          11 :                 CPLAssert(fieldM->type_id() == arrow::Type::DOUBLE);
    2844             :                 const auto fieldMDouble =
    2845          11 :                     static_cast<arrow::DoubleArray *>(fieldM.get());
    2846          11 :                 return new OGRPoint(fieldXDouble->Value(pointOffset),
    2847          11 :                                     fieldYDouble->Value(pointOffset),
    2848          11 :                                     fieldZDouble->Value(pointOffset),
    2849          11 :                                     fieldMDouble->Value(pointOffset));
    2850             :             }
    2851             :             else
    2852             :             {
    2853         395 :                 return new OGRPoint(fieldXDouble->Value(pointOffset),
    2854         395 :                                     fieldYDouble->Value(pointOffset),
    2855         395 :                                     fieldZDouble->Value(pointOffset));
    2856             :             }
    2857             :         }
    2858         517 :         else if (bHasM)
    2859             :         {
    2860          11 :             const auto &fieldM = structArray->field(2);
    2861          11 :             CPLAssert(fieldM->type_id() == arrow::Type::DOUBLE);
    2862             :             const auto fieldMDouble =
    2863          11 :                 static_cast<arrow::DoubleArray *>(fieldM.get());
    2864          11 :             return OGRPoint::createXYM(fieldXDouble->Value(pointOffset),
    2865             :                                        fieldYDouble->Value(pointOffset),
    2866          11 :                                        fieldMDouble->Value(pointOffset));
    2867             :         }
    2868             :         else
    2869             :         {
    2870         506 :             return new OGRPoint(fieldXDouble->Value(pointOffset),
    2871         506 :                                 fieldYDouble->Value(pointOffset));
    2872             :         }
    2873        7962 :     };
    2874             : 
    2875             :     // Arrow 14 since https://github.com/apache/arrow/commit/95a8bfb319b2729c8f6daa069433caba3b4ddddd
    2876             :     // returns reference to shared pointers, so we can safely take the raw pointer
    2877             :     // and cast it.
    2878             :     // Earlier versions returned a non-reference shared pointer, so formally it
    2879             :     // is safer to use static_pointer_cast (although in practice given that
    2880             :     // "values" is a member variable), the Arrow >= 14 path might work...
    2881             : #if ARROW_VERSION_MAJOR >= 14
    2882             : #define GET_PTR_FROM_VALUES(var, type, values)                                 \
    2883             :     const auto var = static_cast<const type *>((values).get())
    2884             : #else
    2885             : #define GET_PTR_FROM_VALUES(var, type, values)                                 \
    2886             :     const auto var##tmp = std::static_pointer_cast<type>(values);              \
    2887             :     const auto var = var##tmp.get()
    2888             : #endif
    2889             : 
    2890        7962 :     switch (m_aeGeomEncoding[iGeomField])
    2891             :     {
    2892        4270 :         case OGRArrowGeomEncoding::WKB:
    2893             :         {
    2894        4270 :             int out_length = 0;
    2895             :             const uint8_t *data;
    2896        4270 :             if (array->type_id() == arrow::Type::BINARY)
    2897             :             {
    2898        4259 :                 const auto castArray =
    2899             :                     static_cast<const arrow::BinaryArray *>(array);
    2900        4259 :                 data = castArray->GetValue(nIdxInBatch, &out_length);
    2901             :             }
    2902             :             else
    2903             :             {
    2904          11 :                 CPLAssert(array->type_id() == arrow::Type::LARGE_BINARY);
    2905          11 :                 const auto castArray =
    2906             :                     static_cast<const arrow::LargeBinaryArray *>(array);
    2907          11 :                 int64_t out_length64 = 0;
    2908          11 :                 data = castArray->GetValue(nIdxInBatch, &out_length64);
    2909          11 :                 if (out_length64 > INT_MAX)
    2910             :                 {
    2911           0 :                     CPLError(CE_Failure, CPLE_AppDefined, "Too large geometry");
    2912           0 :                     return nullptr;
    2913             :                 }
    2914          11 :                 out_length = static_cast<int>(out_length64);
    2915             :             }
    2916        4270 :             if (OGRGeometryFactory::createFromWkb(
    2917        4270 :                     data, poGeomFieldDefn->GetSpatialRef(), &poGeometry,
    2918        4270 :                     out_length) == OGRERR_NONE)
    2919             :             {
    2920             : #ifdef DEBUG_ReadWKBBoundingBox
    2921             :                 OGREnvelope sEnvelopeFromWKB;
    2922             :                 bool bRet =
    2923             :                     OGRWKBGetBoundingBox(data, out_length, sEnvelopeFromWKB);
    2924             :                 CPLAssert(bRet);
    2925             :                 OGREnvelope sEnvelopeFromGeom;
    2926             :                 poGeometry->getEnvelope(&sEnvelopeFromGeom);
    2927             :                 CPLAssert(sEnvelopeFromWKB == sEnvelopeFromGeom);
    2928             : #endif
    2929             :             }
    2930        4270 :             break;
    2931             :         }
    2932             : 
    2933         120 :         case OGRArrowGeomEncoding::WKT:
    2934             :         {
    2935         120 :             if (array->type_id() == arrow::Type::STRING)
    2936             :             {
    2937         119 :                 const auto castArray =
    2938             :                     static_cast<const arrow::StringArray *>(array);
    2939         238 :                 const auto osWKT = castArray->GetString(nIdxInBatch);
    2940         119 :                 OGRGeometryFactory::createFromWkt(
    2941         119 :                     osWKT.c_str(), poGeomFieldDefn->GetSpatialRef(),
    2942             :                     &poGeometry);
    2943             :             }
    2944             :             else
    2945             :             {
    2946           1 :                 CPLAssert(array->type_id() == arrow::Type::LARGE_STRING);
    2947           1 :                 const auto castArray =
    2948             :                     static_cast<const arrow::LargeStringArray *>(array);
    2949           2 :                 const auto osWKT = castArray->GetString(nIdxInBatch);
    2950           1 :                 OGRGeometryFactory::createFromWkt(
    2951           1 :                     osWKT.c_str(), poGeomFieldDefn->GetSpatialRef(),
    2952             :                     &poGeometry);
    2953             :             }
    2954         120 :             break;
    2955             :         }
    2956             : 
    2957           0 :         case OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC:
    2958             :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_GENERIC:
    2959             :         {
    2960           0 :             CPLAssert(false);
    2961             :             break;
    2962             :         }
    2963             : 
    2964         113 :         case OGRArrowGeomEncoding::GEOARROW_FSL_POINT:
    2965             :         {
    2966         113 :             CPLAssert(array->type_id() == arrow::Type::FIXED_SIZE_LIST);
    2967         113 :             const auto listArray =
    2968             :                 static_cast<const arrow::FixedSizeListArray *>(array);
    2969         113 :             CPLAssert(listArray->values()->type_id() == arrow::Type::DOUBLE);
    2970         113 :             GET_PTR_FROM_VALUES(pointValues, arrow::DoubleArray,
    2971             :                                 listArray->values());
    2972         113 :             if (!pointValues->IsNull(nDim * nIdxInBatch))
    2973             :             {
    2974         113 :                 poGeometry = CreatePoint(pointValues,
    2975             :                                          static_cast<int>(nDim * nIdxInBatch));
    2976         113 :                 poGeometry->assignSpatialReference(
    2977         113 :                     poGeomFieldDefn->GetSpatialRef());
    2978             :             }
    2979         113 :             break;
    2980             :         }
    2981             : 
    2982         100 :         case OGRArrowGeomEncoding::GEOARROW_FSL_LINESTRING:
    2983             :         {
    2984         100 :             CPLAssert(array->type_id() == arrow::Type::LIST);
    2985         100 :             const auto listArray = static_cast<const arrow::ListArray *>(array);
    2986         100 :             CPLAssert(listArray->values()->type_id() ==
    2987             :                       arrow::Type::FIXED_SIZE_LIST);
    2988         100 :             GET_PTR_FROM_VALUES(listOfPointsValues, arrow::FixedSizeListArray,
    2989             :                                 listArray->values());
    2990         100 :             CPLAssert(listOfPointsValues->values()->type_id() ==
    2991             :                       arrow::Type::DOUBLE);
    2992         100 :             GET_PTR_FROM_VALUES(pointValues, arrow::DoubleArray,
    2993             :                                 listOfPointsValues->values());
    2994         100 :             const auto nPoints = listArray->value_length(nIdxInBatch);
    2995             :             const auto nPointOffset =
    2996         100 :                 listArray->value_offset(nIdxInBatch) * nDim;
    2997         100 :             auto poLineString = new OGRLineString();
    2998         100 :             poGeometry = poLineString;
    2999         100 :             poGeometry->assignSpatialReference(
    3000         100 :                 poGeomFieldDefn->GetSpatialRef());
    3001         100 :             if (nPoints)
    3002             :             {
    3003          64 :                 GetSetPointsOfLine(bHasZ, bHasM)(poLineString, pointValues,
    3004             :                                                  nPointOffset, nPoints);
    3005             :             }
    3006             :             else
    3007             :             {
    3008          36 :                 poGeometry->set3D(bHasZ);
    3009          36 :                 poGeometry->setMeasured(bHasM);
    3010             :             }
    3011         100 :             break;
    3012             :         }
    3013             : 
    3014         468 :         case OGRArrowGeomEncoding::GEOARROW_FSL_POLYGON:
    3015             :         {
    3016         468 :             CPLAssert(array->type_id() == arrow::Type::LIST);
    3017         468 :             const auto listOfRingsArray =
    3018             :                 static_cast<const arrow::ListArray *>(array);
    3019         468 :             CPLAssert(listOfRingsArray->values()->type_id() ==
    3020             :                       arrow::Type::LIST);
    3021         468 :             GET_PTR_FROM_VALUES(listOfRingsValues, arrow::ListArray,
    3022             :                                 listOfRingsArray->values());
    3023         468 :             CPLAssert(listOfRingsValues->values()->type_id() ==
    3024             :                       arrow::Type::FIXED_SIZE_LIST);
    3025         468 :             GET_PTR_FROM_VALUES(listOfPointsValues, arrow::FixedSizeListArray,
    3026             :                                 listOfRingsValues->values());
    3027         468 :             CPLAssert(listOfPointsValues->values()->type_id() ==
    3028             :                       arrow::Type::DOUBLE);
    3029         468 :             GET_PTR_FROM_VALUES(pointValues, arrow::DoubleArray,
    3030             :                                 listOfPointsValues->values());
    3031         468 :             const auto setPointsFun = GetSetPointsOfLine(bHasZ, bHasM);
    3032         468 :             const auto nRings = listOfRingsArray->value_length(nIdxInBatch);
    3033             :             const auto nRingOffset =
    3034         468 :                 listOfRingsArray->value_offset(nIdxInBatch);
    3035         468 :             auto poPoly = new OGRPolygon();
    3036         468 :             poGeometry = poPoly;
    3037         468 :             poGeometry->assignSpatialReference(
    3038         468 :                 poGeomFieldDefn->GetSpatialRef());
    3039         954 :             for (auto k = decltype(nRings){0}; k < nRings; k++)
    3040             :             {
    3041             :                 const auto nPoints =
    3042         486 :                     listOfRingsValues->value_length(nRingOffset + k);
    3043             :                 const auto nPointOffset =
    3044         486 :                     listOfRingsValues->value_offset(nRingOffset + k) * nDim;
    3045         486 :                 auto poRing = new OGRLinearRing();
    3046         486 :                 if (nPoints)
    3047             :                 {
    3048         486 :                     setPointsFun(poRing, pointValues, nPointOffset, nPoints);
    3049             :                 }
    3050         486 :                 poPoly->addRingDirectly(poRing);
    3051             :             }
    3052         468 :             if (poGeometry->IsEmpty())
    3053             :             {
    3054         122 :                 poGeometry->set3D(bHasZ);
    3055         122 :                 poGeometry->setMeasured(bHasM);
    3056             :             }
    3057         468 :             break;
    3058             :         }
    3059             : 
    3060         148 :         case OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOINT:
    3061             :         {
    3062         148 :             CPLAssert(array->type_id() == arrow::Type::LIST);
    3063         148 :             const auto listArray = static_cast<const arrow::ListArray *>(array);
    3064         148 :             CPLAssert(listArray->values()->type_id() ==
    3065             :                       arrow::Type::FIXED_SIZE_LIST);
    3066         148 :             GET_PTR_FROM_VALUES(listOfPointsValues, arrow::FixedSizeListArray,
    3067             :                                 listArray->values());
    3068         148 :             CPLAssert(listOfPointsValues->values()->type_id() ==
    3069             :                       arrow::Type::DOUBLE);
    3070         148 :             GET_PTR_FROM_VALUES(pointValues, arrow::DoubleArray,
    3071             :                                 listOfPointsValues->values());
    3072         148 :             const auto nPoints = listArray->value_length(nIdxInBatch);
    3073             :             const auto nPointOffset =
    3074         148 :                 listArray->value_offset(nIdxInBatch) * nDim;
    3075         148 :             auto poMultiPoint = new OGRMultiPoint();
    3076         148 :             poGeometry = poMultiPoint;
    3077         148 :             poGeometry->assignSpatialReference(
    3078         148 :                 poGeomFieldDefn->GetSpatialRef());
    3079         434 :             for (auto k = decltype(nPoints){0}; k < nPoints; k++)
    3080             :             {
    3081         572 :                 poMultiPoint->addGeometryDirectly(
    3082         286 :                     CreatePoint(pointValues, nPointOffset + k * nDim));
    3083             :             }
    3084         148 :             if (poGeometry->IsEmpty())
    3085             :             {
    3086          34 :                 poGeometry->set3D(bHasZ);
    3087          34 :                 poGeometry->setMeasured(bHasM);
    3088             :             }
    3089         148 :             break;
    3090             :         }
    3091             : 
    3092         144 :         case OGRArrowGeomEncoding::GEOARROW_FSL_MULTILINESTRING:
    3093             :         {
    3094         144 :             CPLAssert(array->type_id() == arrow::Type::LIST);
    3095         144 :             const auto listOfStringsArray =
    3096             :                 static_cast<const arrow::ListArray *>(array);
    3097         144 :             CPLAssert(listOfStringsArray->values()->type_id() ==
    3098             :                       arrow::Type::LIST);
    3099         144 :             GET_PTR_FROM_VALUES(listOfStringsValues, arrow::ListArray,
    3100             :                                 listOfStringsArray->values());
    3101         144 :             CPLAssert(listOfStringsValues->values()->type_id() ==
    3102             :                       arrow::Type::FIXED_SIZE_LIST);
    3103         144 :             GET_PTR_FROM_VALUES(listOfPointsValues, arrow::FixedSizeListArray,
    3104             :                                 listOfStringsValues->values());
    3105         144 :             CPLAssert(listOfPointsValues->values()->type_id() ==
    3106             :                       arrow::Type::DOUBLE);
    3107         144 :             GET_PTR_FROM_VALUES(pointValues, arrow::DoubleArray,
    3108             :                                 listOfPointsValues->values());
    3109         144 :             const auto setPointsFun = GetSetPointsOfLine(bHasZ, bHasM);
    3110         144 :             const auto nStrings = listOfStringsArray->value_length(nIdxInBatch);
    3111             :             const auto nRingOffset =
    3112         144 :                 listOfStringsArray->value_offset(nIdxInBatch);
    3113         144 :             auto poMLS = new OGRMultiLineString();
    3114         144 :             poGeometry = poMLS;
    3115         144 :             poGeometry->assignSpatialReference(
    3116         144 :                 poGeomFieldDefn->GetSpatialRef());
    3117         316 :             for (auto k = decltype(nStrings){0}; k < nStrings; k++)
    3118             :             {
    3119             :                 const auto nPoints =
    3120         172 :                     listOfStringsValues->value_length(nRingOffset + k);
    3121             :                 const auto nPointOffset =
    3122         172 :                     listOfStringsValues->value_offset(nRingOffset + k) * nDim;
    3123         172 :                 auto poLS = new OGRLineString();
    3124         172 :                 if (nPoints)
    3125             :                 {
    3126         172 :                     setPointsFun(poLS, pointValues, nPointOffset, nPoints);
    3127             :                 }
    3128         172 :                 poMLS->addGeometryDirectly(poLS);
    3129             :             }
    3130         144 :             if (poGeometry->IsEmpty())
    3131             :             {
    3132          36 :                 poGeometry->set3D(bHasZ);
    3133          36 :                 poGeometry->setMeasured(bHasM);
    3134             :             }
    3135         144 :             break;
    3136             :         }
    3137             : 
    3138         160 :         case OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOLYGON:
    3139             :         {
    3140         160 :             CPLAssert(array->type_id() == arrow::Type::LIST);
    3141         160 :             const auto listOfPartsArray =
    3142             :                 static_cast<const arrow::ListArray *>(array);
    3143         160 :             CPLAssert(listOfPartsArray->values()->type_id() ==
    3144             :                       arrow::Type::LIST);
    3145         160 :             GET_PTR_FROM_VALUES(listOfPartsValues, arrow::ListArray,
    3146             :                                 listOfPartsArray->values());
    3147         160 :             CPLAssert(listOfPartsValues->values()->type_id() ==
    3148             :                       arrow::Type::LIST);
    3149         160 :             GET_PTR_FROM_VALUES(listOfRingsValues, arrow::ListArray,
    3150             :                                 listOfPartsValues->values());
    3151         160 :             CPLAssert(listOfRingsValues->values()->type_id() ==
    3152             :                       arrow::Type::FIXED_SIZE_LIST);
    3153         160 :             GET_PTR_FROM_VALUES(listOfPointsValues, arrow::FixedSizeListArray,
    3154             :                                 listOfRingsValues->values());
    3155         160 :             CPLAssert(listOfPointsValues->values()->type_id() ==
    3156             :                       arrow::Type::DOUBLE);
    3157         160 :             GET_PTR_FROM_VALUES(pointValues, arrow::DoubleArray,
    3158             :                                 listOfPointsValues->values());
    3159         160 :             auto poMP = new OGRMultiPolygon();
    3160         160 :             poGeometry = poMP;
    3161         160 :             poGeometry->assignSpatialReference(
    3162         160 :                 poGeomFieldDefn->GetSpatialRef());
    3163         160 :             const auto setPointsFun = GetSetPointsOfLine(bHasZ, bHasM);
    3164         160 :             const auto nParts = listOfPartsArray->value_length(nIdxInBatch);
    3165             :             const auto nPartOffset =
    3166         160 :                 listOfPartsArray->value_offset(nIdxInBatch);
    3167         356 :             for (auto j = decltype(nParts){0}; j < nParts; j++)
    3168             :             {
    3169             :                 const auto nRings =
    3170         196 :                     listOfPartsValues->value_length(nPartOffset + j);
    3171             :                 const auto nRingOffset =
    3172         196 :                     listOfPartsValues->value_offset(nPartOffset + j);
    3173         196 :                 auto poPoly = new OGRPolygon();
    3174         434 :                 for (auto k = decltype(nRings){0}; k < nRings; k++)
    3175             :                 {
    3176             :                     const auto nPoints =
    3177         238 :                         listOfRingsValues->value_length(nRingOffset + k);
    3178             :                     const auto nPointOffset =
    3179         238 :                         listOfRingsValues->value_offset(nRingOffset + k) * nDim;
    3180         238 :                     auto poRing = new OGRLinearRing();
    3181         238 :                     if (nPoints)
    3182             :                     {
    3183         238 :                         setPointsFun(poRing, pointValues, nPointOffset,
    3184             :                                      nPoints);
    3185             :                     }
    3186         238 :                     poPoly->addRingDirectly(poRing);
    3187             :                 }
    3188         196 :                 poMP->addGeometryDirectly(poPoly);
    3189             :             }
    3190         160 :             if (poGeometry->IsEmpty())
    3191             :             {
    3192          36 :                 poGeometry->set3D(bHasZ);
    3193          36 :                 poGeometry->setMeasured(bHasM);
    3194             :             }
    3195         160 :             break;
    3196             :         }
    3197             : 
    3198         407 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_POINT:
    3199             :         {
    3200         407 :             CPLAssert(array->type_id() == arrow::Type::STRUCT);
    3201         407 :             const auto structArray =
    3202             :                 static_cast<const arrow::StructArray *>(array);
    3203         407 :             if (!structArray->IsNull(nIdxInBatch))
    3204             :             {
    3205         407 :                 poGeometry = CreateStructPoint(structArray, nIdxInBatch);
    3206         407 :                 poGeometry->assignSpatialReference(
    3207         407 :                     poGeomFieldDefn->GetSpatialRef());
    3208             :             }
    3209         407 :             break;
    3210             :         }
    3211             : 
    3212         296 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_LINESTRING:
    3213             :         {
    3214         296 :             CPLAssert(array->type_id() == arrow::Type::LIST);
    3215         296 :             const auto listArray = static_cast<const arrow::ListArray *>(array);
    3216         296 :             CPLAssert(listArray->values()->type_id() == arrow::Type::STRUCT);
    3217         296 :             GET_PTR_FROM_VALUES(pointValues, arrow::StructArray,
    3218             :                                 listArray->values());
    3219         296 :             const auto nPoints = listArray->value_length(nIdxInBatch);
    3220         296 :             const auto nPointOffset = listArray->value_offset(nIdxInBatch);
    3221         296 :             auto poLineString = new OGRLineString();
    3222         296 :             poGeometry = poLineString;
    3223         296 :             poGeometry->assignSpatialReference(
    3224         296 :                 poGeomFieldDefn->GetSpatialRef());
    3225         296 :             if (nPoints)
    3226             :             {
    3227         244 :                 GetSetPointsOfLineStruct(bHasZ, bHasM)(
    3228             :                     poLineString, pointValues, nPointOffset, nPoints);
    3229             :             }
    3230             :             else
    3231             :             {
    3232          52 :                 poGeometry->set3D(bHasZ);
    3233          52 :                 poGeometry->setMeasured(bHasM);
    3234             :             }
    3235         296 :             break;
    3236             :         }
    3237             : 
    3238         468 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_POLYGON:
    3239             :         {
    3240         468 :             CPLAssert(array->type_id() == arrow::Type::LIST);
    3241         468 :             const auto listOfRingsArray =
    3242             :                 static_cast<const arrow::ListArray *>(array);
    3243         468 :             CPLAssert(listOfRingsArray->values()->type_id() ==
    3244             :                       arrow::Type::LIST);
    3245         468 :             GET_PTR_FROM_VALUES(listOfRingsValues, arrow::ListArray,
    3246             :                                 listOfRingsArray->values());
    3247         468 :             CPLAssert(listOfRingsValues->values()->type_id() ==
    3248             :                       arrow::Type::STRUCT);
    3249         468 :             GET_PTR_FROM_VALUES(pointValues, arrow::StructArray,
    3250             :                                 listOfRingsValues->values());
    3251         468 :             const auto setPointsFun = GetSetPointsOfLineStruct(bHasZ, bHasM);
    3252         468 :             const auto nRings = listOfRingsArray->value_length(nIdxInBatch);
    3253             :             const auto nRingOffset =
    3254         468 :                 listOfRingsArray->value_offset(nIdxInBatch);
    3255         468 :             auto poPoly = new OGRPolygon();
    3256         468 :             poGeometry = poPoly;
    3257         468 :             poGeometry->assignSpatialReference(
    3258         468 :                 poGeomFieldDefn->GetSpatialRef());
    3259         960 :             for (auto k = decltype(nRings){0}; k < nRings; k++)
    3260             :             {
    3261             :                 const auto nPoints =
    3262         492 :                     listOfRingsValues->value_length(nRingOffset + k);
    3263             :                 const auto nPointOffset =
    3264         492 :                     listOfRingsValues->value_offset(nRingOffset + k);
    3265         492 :                 auto poRing = new OGRLinearRing();
    3266         492 :                 if (nPoints)
    3267             :                 {
    3268         492 :                     setPointsFun(poRing, pointValues, nPointOffset, nPoints);
    3269             :                 }
    3270         492 :                 poPoly->addRingDirectly(poRing);
    3271             :             }
    3272         468 :             if (poGeometry->IsEmpty())
    3273             :             {
    3274          76 :                 poGeometry->set3D(bHasZ);
    3275          76 :                 poGeometry->setMeasured(bHasM);
    3276             :             }
    3277         468 :             break;
    3278             :         }
    3279             : 
    3280         352 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOINT:
    3281             :         {
    3282         352 :             CPLAssert(array->type_id() == arrow::Type::LIST);
    3283         352 :             const auto listArray = static_cast<const arrow::ListArray *>(array);
    3284         352 :             CPLAssert(listArray->values()->type_id() == arrow::Type::STRUCT);
    3285         352 :             GET_PTR_FROM_VALUES(pointValues, arrow::StructArray,
    3286             :                                 listArray->values());
    3287         352 :             const auto nPoints = listArray->value_length(nIdxInBatch);
    3288         352 :             const auto nPointOffset = listArray->value_offset(nIdxInBatch);
    3289         352 :             auto poMultiPoint = new OGRMultiPoint();
    3290         352 :             poGeometry = poMultiPoint;
    3291         352 :             poGeometry->assignSpatialReference(
    3292         352 :                 poGeomFieldDefn->GetSpatialRef());
    3293         868 :             for (auto k = decltype(nPoints){0}; k < nPoints; k++)
    3294             :             {
    3295        1032 :                 poMultiPoint->addGeometryDirectly(
    3296         516 :                     CreateStructPoint(pointValues, nPointOffset + k));
    3297             :             }
    3298         352 :             if (poGeometry->IsEmpty())
    3299             :             {
    3300          52 :                 poGeometry->set3D(bHasZ);
    3301          52 :                 poGeometry->setMeasured(bHasM);
    3302             :             }
    3303         352 :             break;
    3304             :         }
    3305             : 
    3306         420 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTILINESTRING:
    3307             :         {
    3308         420 :             CPLAssert(array->type_id() == arrow::Type::LIST);
    3309         420 :             const auto listOfStringsArray =
    3310             :                 static_cast<const arrow::ListArray *>(array);
    3311         420 :             CPLAssert(listOfStringsArray->values()->type_id() ==
    3312             :                       arrow::Type::LIST);
    3313         420 :             GET_PTR_FROM_VALUES(listOfStringsValues, arrow::ListArray,
    3314             :                                 listOfStringsArray->values());
    3315         420 :             CPLAssert(listOfStringsValues->values()->type_id() ==
    3316             :                       arrow::Type::STRUCT);
    3317         420 :             GET_PTR_FROM_VALUES(pointValues, arrow::StructArray,
    3318             :                                 listOfStringsValues->values());
    3319         420 :             const auto setPointsFun = GetSetPointsOfLineStruct(bHasZ, bHasM);
    3320         420 :             const auto nStrings = listOfStringsArray->value_length(nIdxInBatch);
    3321             :             const auto nRingOffset =
    3322         420 :                 listOfStringsArray->value_offset(nIdxInBatch);
    3323         420 :             auto poMLS = new OGRMultiLineString();
    3324         420 :             poGeometry = poMLS;
    3325         420 :             poGeometry->assignSpatialReference(
    3326         420 :                 poGeomFieldDefn->GetSpatialRef());
    3327        1032 :             for (auto k = decltype(nStrings){0}; k < nStrings; k++)
    3328             :             {
    3329             :                 const auto nPoints =
    3330         612 :                     listOfStringsValues->value_length(nRingOffset + k);
    3331             :                 const auto nPointOffset =
    3332         612 :                     listOfStringsValues->value_offset(nRingOffset + k);
    3333         612 :                 auto poLS = new OGRLineString();
    3334         612 :                 if (nPoints)
    3335             :                 {
    3336         612 :                     setPointsFun(poLS, pointValues, nPointOffset, nPoints);
    3337             :                 }
    3338         612 :                 poMLS->addGeometryDirectly(poLS);
    3339             :             }
    3340         420 :             if (poGeometry->IsEmpty())
    3341             :             {
    3342          52 :                 poGeometry->set3D(bHasZ);
    3343          52 :                 poGeometry->setMeasured(bHasM);
    3344             :             }
    3345         420 :             break;
    3346             :         }
    3347             : 
    3348         496 :         case OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOLYGON:
    3349             :         {
    3350         496 :             CPLAssert(array->type_id() == arrow::Type::LIST);
    3351         496 :             const auto listOfPartsArray =
    3352             :                 static_cast<const arrow::ListArray *>(array);
    3353         496 :             CPLAssert(listOfPartsArray->values()->type_id() ==
    3354             :                       arrow::Type::LIST);
    3355         496 :             GET_PTR_FROM_VALUES(listOfPartsValues, arrow::ListArray,
    3356             :                                 listOfPartsArray->values());
    3357         496 :             CPLAssert(listOfPartsValues->values()->type_id() ==
    3358             :                       arrow::Type::LIST);
    3359         496 :             GET_PTR_FROM_VALUES(listOfRingsValues, arrow::ListArray,
    3360             :                                 listOfPartsValues->values());
    3361         496 :             CPLAssert(listOfRingsValues->values()->type_id() ==
    3362             :                       arrow::Type::STRUCT);
    3363         496 :             GET_PTR_FROM_VALUES(pointValues, arrow::StructArray,
    3364             :                                 listOfRingsValues->values());
    3365         496 :             auto poMP = new OGRMultiPolygon();
    3366         496 :             poGeometry = poMP;
    3367         496 :             poGeometry->assignSpatialReference(
    3368         496 :                 poGeomFieldDefn->GetSpatialRef());
    3369         496 :             const auto setPointsFun = GetSetPointsOfLineStruct(bHasZ, bHasM);
    3370         496 :             const auto nParts = listOfPartsArray->value_length(nIdxInBatch);
    3371             :             const auto nPartOffset =
    3372         496 :                 listOfPartsArray->value_offset(nIdxInBatch);
    3373        1116 :             for (auto j = decltype(nParts){0}; j < nParts; j++)
    3374             :             {
    3375             :                 const auto nRings =
    3376         620 :                     listOfPartsValues->value_length(nPartOffset + j);
    3377             :                 const auto nRingOffset =
    3378         620 :                     listOfPartsValues->value_offset(nPartOffset + j);
    3379         620 :                 auto poPoly = new OGRPolygon();
    3380        1388 :                 for (auto k = decltype(nRings){0}; k < nRings; k++)
    3381             :                 {
    3382             :                     const auto nPoints =
    3383         768 :                         listOfRingsValues->value_length(nRingOffset + k);
    3384             :                     const auto nPointOffset =
    3385         768 :                         listOfRingsValues->value_offset(nRingOffset + k);
    3386         768 :                     auto poRing = new OGRLinearRing();
    3387         768 :                     if (nPoints)
    3388             :                     {
    3389         768 :                         setPointsFun(poRing, pointValues, nPointOffset,
    3390             :                                      nPoints);
    3391             :                     }
    3392         768 :                     poPoly->addRingDirectly(poRing);
    3393             :                 }
    3394         620 :                 poMP->addGeometryDirectly(poPoly);
    3395             :             }
    3396         496 :             if (poGeometry->IsEmpty())
    3397             :             {
    3398          76 :                 poGeometry->set3D(bHasZ);
    3399          76 :                 poGeometry->setMeasured(bHasM);
    3400             :             }
    3401         496 :             break;
    3402             :         }
    3403             :     }
    3404        7962 :     return poGeometry;
    3405             : }
    3406             : 
    3407             : /************************************************************************/
    3408             : /*                            ResetReading()                            */
    3409             : /************************************************************************/
    3410             : 
    3411        8891 : inline void OGRArrowLayer::ResetReading()
    3412             : {
    3413        8891 :     m_bEOF = false;
    3414        8891 :     m_nFeatureIdx = 0;
    3415        8891 :     m_nIdxInBatch = 0;
    3416        8891 :     m_poReadFeatureTmpArray.reset();
    3417        8891 :     if (m_iRecordBatch != 0)
    3418             :     {
    3419        7922 :         m_iRecordBatch = -1;
    3420        7922 :         m_poBatch.reset();
    3421        7922 :         m_poBatchColumns.clear();
    3422             :     }
    3423        8891 : }
    3424             : 
    3425             : /************************************************************************/
    3426             : /*                          GetColumnSubNode()                          */
    3427             : /************************************************************************/
    3428             : 
    3429             : /* static*/
    3430             : inline const swq_expr_node *
    3431         290 : OGRArrowLayer::GetColumnSubNode(const swq_expr_node *poNode)
    3432             : {
    3433         290 :     if (poNode->eNodeType == SNT_OPERATION && poNode->nSubExprCount == 2)
    3434             :     {
    3435         290 :         if (poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN)
    3436         208 :             return poNode->papoSubExpr[0];
    3437          82 :         if (poNode->papoSubExpr[1]->eNodeType == SNT_COLUMN)
    3438          12 :             return poNode->papoSubExpr[1];
    3439             :     }
    3440          70 :     return nullptr;
    3441             : }
    3442             : 
    3443             : /************************************************************************/
    3444             : /*                         GetConstantSubNode()                         */
    3445             : /************************************************************************/
    3446             : 
    3447             : /* static */
    3448             : inline const swq_expr_node *
    3449         290 : OGRArrowLayer::GetConstantSubNode(const swq_expr_node *poNode)
    3450             : {
    3451         290 :     if (poNode->eNodeType == SNT_OPERATION && poNode->nSubExprCount == 2)
    3452             :     {
    3453         290 :         if (poNode->papoSubExpr[1]->eNodeType == SNT_CONSTANT)
    3454         276 :             return poNode->papoSubExpr[1];
    3455          14 :         if (poNode->papoSubExpr[0]->eNodeType == SNT_CONSTANT)
    3456          12 :             return poNode->papoSubExpr[0];
    3457             :     }
    3458           2 :     return nullptr;
    3459             : }
    3460             : 
    3461             : /************************************************************************/
    3462             : /*                           IsComparisonOp()                           */
    3463             : /************************************************************************/
    3464             : 
    3465             : /* static*/
    3466         440 : inline bool OGRArrowLayer::IsComparisonOp(int op)
    3467             : {
    3468         166 :     return (op == SWQ_EQ || op == SWQ_NE || op == SWQ_LT || op == SWQ_LE ||
    3469         606 :             op == SWQ_GT || op == SWQ_GE);
    3470             : }
    3471             : 
    3472             : /************************************************************************/
    3473             : /*                     FillTargetValueFromSrcExpr()                     */
    3474             : /************************************************************************/
    3475             : 
    3476         218 : static bool FillTargetValueFromSrcExpr(const OGRFieldDefn *poFieldDefn,
    3477             :                                        OGRArrowLayer::Constraint *psConstraint,
    3478             :                                        const swq_expr_node *poSrcValue)
    3479             : {
    3480         218 :     switch (poFieldDefn->GetType())
    3481             :     {
    3482         112 :         case OFTInteger:
    3483         112 :             psConstraint->eType = OGRArrowLayer::Constraint::Type::Integer;
    3484         112 :             if (poSrcValue->field_type == SWQ_FLOAT)
    3485           2 :                 psConstraint->sValue.Integer =
    3486           2 :                     static_cast<int>(poSrcValue->float_value);
    3487             :             else
    3488         110 :                 psConstraint->sValue.Integer =
    3489         110 :                     static_cast<int>(poSrcValue->int_value);
    3490             :             psConstraint->osValue =
    3491         112 :                 std::to_string(psConstraint->sValue.Integer);
    3492         112 :             break;
    3493             : 
    3494          30 :         case OFTInteger64:
    3495          30 :             psConstraint->eType = OGRArrowLayer::Constraint::Type::Integer64;
    3496          30 :             if (poSrcValue->field_type == SWQ_FLOAT)
    3497           0 :                 psConstraint->sValue.Integer64 =
    3498           0 :                     static_cast<GIntBig>(poSrcValue->float_value);
    3499             :             else
    3500          30 :                 psConstraint->sValue.Integer64 = poSrcValue->int_value;
    3501             :             psConstraint->osValue =
    3502          30 :                 std::to_string(psConstraint->sValue.Integer64);
    3503          30 :             break;
    3504             : 
    3505          25 :         case OFTReal:
    3506          25 :             psConstraint->eType = OGRArrowLayer::Constraint::Type::Real;
    3507          25 :             psConstraint->sValue.Real = poSrcValue->float_value;
    3508          25 :             psConstraint->osValue = std::to_string(psConstraint->sValue.Real);
    3509          25 :             break;
    3510             : 
    3511          26 :         case OFTString:
    3512          26 :             psConstraint->eType = OGRArrowLayer::Constraint::Type::String;
    3513          26 :             psConstraint->sValue.String = poSrcValue->string_value;
    3514          26 :             psConstraint->osValue = psConstraint->sValue.String;
    3515          26 :             break;
    3516             : #ifdef not_yet_handled
    3517             :         case OFTDate:
    3518             :         case OFTTime:
    3519             :         case OFTDateTime:
    3520             :             if (poSrcValue->field_type == SWQ_TIMESTAMP ||
    3521             :                 poSrcValue->field_type == SWQ_DATE ||
    3522             :                 poSrcValue->field_type == SWQ_TIME)
    3523             :             {
    3524             :                 int nYear = 0, nMonth = 0, nDay = 0, nHour = 0, nMin = 0,
    3525             :                     nSec = 0;
    3526             :                 if (sscanf(poSrcValue->string_value,
    3527             :                            "%04d/%02d/%02d %02d:%02d:%02d", &nYear, &nMonth,
    3528             :                            &nDay, &nHour, &nMin, &nSec) == 6 ||
    3529             :                     sscanf(poSrcValue->string_value, "%04d/%02d/%02d", &nYear,
    3530             :                            &nMonth, &nDay) == 3 ||
    3531             :                     sscanf(poSrcValue->string_value, "%02d:%02d:%02d", &nHour,
    3532             :                            &nMin, &nSec) == 3)
    3533             :                 {
    3534             :                     psConstraint->eType =
    3535             :                         OGRArrowLayer::Constraint::Type::DateTime;
    3536             :                     psConstraint->sValue.Date.Year = (GInt16)nYear;
    3537             :                     psConstraint->sValue.Date.Month = (GByte)nMonth;
    3538             :                     psConstraint->sValue.Date.Day = (GByte)nDay;
    3539             :                     psConstraint->sValue.Date.Hour = (GByte)nHour;
    3540             :                     psConstraint->sValue.Date.Minute = (GByte)nMin;
    3541             :                     psConstraint->sValue.Date.Second = (GByte)nSec;
    3542             :                     psConstraint->sValue.Date.TZFlag = 0;
    3543             :                     psConstraint->sValue.Date.Reserved = 0;
    3544             :                 }
    3545             :                 else
    3546             :                     return false;
    3547             :             }
    3548             :             else
    3549             :                 return false;
    3550             :             break;
    3551             : #endif
    3552          25 :         default:
    3553          25 :             return false;
    3554             :     }
    3555         193 :     return true;
    3556             : }
    3557             : 
    3558             : /************************************************************************/
    3559             : /*                     ComputeConstraintsArrayIdx()                     */
    3560             : /************************************************************************/
    3561             : 
    3562         576 : inline void OGRArrowLayer::ComputeConstraintsArrayIdx()
    3563             : {
    3564         783 :     for (auto &constraint : m_asAttributeFilterConstraints)
    3565             :     {
    3566         208 :         if (m_bIgnoredFields)
    3567             :         {
    3568          25 :             if (constraint.iField == m_poFeatureDefn->GetFieldCount() + SPF_FID)
    3569             :             {
    3570           1 :                 constraint.iArrayIdx = m_nRequestedFIDColumn;
    3571           1 :                 if (constraint.iArrayIdx < 0 && m_osFIDColumn.empty())
    3572           1 :                     return;
    3573             :             }
    3574             :             else
    3575             :             {
    3576          24 :                 constraint.iArrayIdx =
    3577          24 :                     m_anMapFieldIndexToArrayIndex[constraint.iField];
    3578             :             }
    3579          24 :             if (constraint.iArrayIdx < 0)
    3580             :             {
    3581           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3582             :                          "Constraint on field %s cannot be applied due to "
    3583             :                          "it being ignored",
    3584           1 :                          constraint.iField ==
    3585           1 :                                  m_poFeatureDefn->GetFieldCount() + SPF_FID
    3586           0 :                              ? m_osFIDColumn.c_str()
    3587           1 :                              : m_poFeatureDefn->GetFieldDefn(constraint.iField)
    3588           1 :                                    ->GetNameRef());
    3589             :             }
    3590             :         }
    3591             :         else
    3592             :         {
    3593         183 :             if (constraint.iField == m_poFeatureDefn->GetFieldCount() + SPF_FID)
    3594             :             {
    3595           8 :                 constraint.iArrayIdx = m_iFIDArrowColumn;
    3596           8 :                 if (constraint.iArrayIdx < 0 && !m_osFIDColumn.empty())
    3597             :                 {
    3598           0 :                     CPLDebug(GetDriverUCName().c_str(),
    3599             :                              "Constraint on field %s cannot be applied",
    3600             :                              m_osFIDColumn.c_str());
    3601             :                 }
    3602             :             }
    3603             :             else
    3604             :             {
    3605         175 :                 constraint.iArrayIdx =
    3606         175 :                     m_anMapFieldIndexToArrowColumn[constraint.iField][0];
    3607             :             }
    3608             :         }
    3609             :     }
    3610             : }
    3611             : 
    3612             : /************************************************************************/
    3613             : /*                          ExploreExprNode()                           */
    3614             : /************************************************************************/
    3615             : 
    3616         354 : inline void OGRArrowLayer::ExploreExprNode(const swq_expr_node *poNode)
    3617             : {
    3618         205 :     const auto AddConstraint = [this](Constraint &constraint)
    3619         205 :     { m_asAttributeFilterConstraints.emplace_back(constraint); };
    3620             : 
    3621         354 :     if (poNode->eNodeType == SNT_OPERATION && poNode->nOperation == SWQ_AND &&
    3622          19 :         poNode->nSubExprCount == 2)
    3623             :     {
    3624          19 :         ExploreExprNode(poNode->papoSubExpr[0]);
    3625          19 :         ExploreExprNode(poNode->papoSubExpr[1]);
    3626             :     }
    3627             : 
    3628         335 :     else if (poNode->eNodeType == SNT_OPERATION &&
    3629         670 :              IsComparisonOp(poNode->nOperation) && poNode->nSubExprCount == 2)
    3630             :     {
    3631         290 :         const swq_expr_node *poColumn = GetColumnSubNode(poNode);
    3632         290 :         const swq_expr_node *poValue = GetConstantSubNode(poNode);
    3633         508 :         if (poColumn != nullptr && poValue != nullptr &&
    3634         218 :             (poColumn->field_index < m_poFeatureDefn->GetFieldCount() ||
    3635           8 :              poColumn->field_index ==
    3636           8 :                  m_poFeatureDefn->GetFieldCount() + SPF_FID))
    3637             :         {
    3638             :             const OGRFieldDefn oDummyFIDFieldDefn(m_osFIDColumn.c_str(),
    3639         436 :                                                   OFTInteger64);
    3640             :             const OGRFieldDefn *poFieldDefn =
    3641         218 :                 (poColumn->field_index ==
    3642         218 :                  m_poFeatureDefn->GetFieldCount() + SPF_FID)
    3643         218 :                     ? &oDummyFIDFieldDefn
    3644         210 :                     : m_poFeatureDefn->GetFieldDefn(poColumn->field_index);
    3645             : 
    3646         436 :             Constraint constraint;
    3647         218 :             constraint.iField = poColumn->field_index;
    3648         218 :             constraint.nOperation = poNode->nOperation;
    3649             : 
    3650         218 :             if (FillTargetValueFromSrcExpr(poFieldDefn, &constraint, poValue))
    3651             :             {
    3652         193 :                 if (poColumn == poNode->papoSubExpr[0])
    3653             :                 {
    3654             :                     // nothing to do
    3655             :                 }
    3656             :                 else
    3657             :                 {
    3658             :                     /* If "constant op column", then we must reverse */
    3659             :                     /* the operator for LE, LT, GE, GT */
    3660          12 :                     switch (poNode->nOperation)
    3661             :                     {
    3662           2 :                         case SWQ_LE:
    3663           2 :                             constraint.nOperation = SWQ_GE;
    3664           2 :                             break;
    3665           2 :                         case SWQ_LT:
    3666           2 :                             constraint.nOperation = SWQ_GT;
    3667           2 :                             break;
    3668           2 :                         case SWQ_NE: /* do nothing */;
    3669           2 :                             break;
    3670           2 :                         case SWQ_EQ: /* do nothing */;
    3671           2 :                             break;
    3672           2 :                         case SWQ_GE:
    3673           2 :                             constraint.nOperation = SWQ_LE;
    3674           2 :                             break;
    3675           2 :                         case SWQ_GT:
    3676           2 :                             constraint.nOperation = SWQ_LT;
    3677           2 :                             break;
    3678           0 :                         default:
    3679           0 :                             CPLAssert(false);
    3680             :                             break;
    3681             :                     }
    3682             :                 }
    3683             : 
    3684         193 :                 AddConstraint(constraint);
    3685             :             }
    3686             :         }
    3687             :     }
    3688             : 
    3689          45 :     else if (poNode->eNodeType == SNT_OPERATION &&
    3690          45 :              poNode->nOperation == SWQ_ISNULL && poNode->nSubExprCount == 1)
    3691             :     {
    3692           4 :         const swq_expr_node *poColumn = poNode->papoSubExpr[0];
    3693           8 :         if (poColumn->eNodeType == SNT_COLUMN &&
    3694           4 :             poColumn->field_index < m_poFeatureDefn->GetFieldCount())
    3695             :         {
    3696           8 :             Constraint constraint;
    3697           4 :             constraint.iField = poColumn->field_index;
    3698           4 :             constraint.nOperation = poNode->nOperation;
    3699           4 :             AddConstraint(constraint);
    3700           4 :         }
    3701             :     }
    3702             : 
    3703          41 :     else if (poNode->eNodeType == SNT_OPERATION &&
    3704          41 :              poNode->nOperation == SWQ_NOT && poNode->nSubExprCount == 1 &&
    3705          20 :              poNode->papoSubExpr[0]->eNodeType == SNT_OPERATION &&
    3706          20 :              poNode->papoSubExpr[0]->nOperation == SWQ_ISNULL &&
    3707          16 :              poNode->papoSubExpr[0]->nSubExprCount == 1)
    3708             :     {
    3709          16 :         const swq_expr_node *poColumn = poNode->papoSubExpr[0]->papoSubExpr[0];
    3710          32 :         if (poColumn->eNodeType == SNT_COLUMN &&
    3711          16 :             poColumn->field_index < m_poFeatureDefn->GetFieldCount())
    3712             :         {
    3713          16 :             Constraint constraint;
    3714           8 :             constraint.iField = poColumn->field_index;
    3715           8 :             constraint.nOperation = SWQ_ISNOTNULL;
    3716           8 :             AddConstraint(constraint);
    3717             :         }
    3718             :     }
    3719         354 : }
    3720             : 
    3721             : /************************************************************************/
    3722             : /*                         SetAttributeFilter()                         */
    3723             : /************************************************************************/
    3724             : 
    3725         755 : inline OGRErr OGRArrowLayer::SetAttributeFilter(const char *pszFilter)
    3726             : {
    3727         755 :     m_asAttributeFilterConstraints.clear();
    3728             : 
    3729             :     // When changing filters, we need to invalidate cached batches, as
    3730             :     // PostFilterArrowArray() has potentially modified array contents
    3731         755 :     if (m_poAttrQuery)
    3732         111 :         InvalidateCachedBatches();
    3733             : 
    3734         755 :     OGRErr eErr = OGRLayer::SetAttributeFilter(pszFilter);
    3735         755 :     if (eErr != OGRERR_NONE)
    3736           0 :         return eErr;
    3737             : 
    3738         755 :     if (m_poAttrQuery != nullptr)
    3739             :     {
    3740         466 :         if (m_nUseOptimizedAttributeFilter < 0)
    3741             :         {
    3742         368 :             m_nUseOptimizedAttributeFilter = CPLTestBool(CPLGetConfigOption(
    3743         736 :                 ("OGR_" + GetDriverUCName() + "_OPTIMIZED_ATTRIBUTE_FILTER")
    3744             :                     .c_str(),
    3745             :                 "YES"));
    3746             :         }
    3747         466 :         if (m_nUseOptimizedAttributeFilter)
    3748             :         {
    3749             :             swq_expr_node *poNode =
    3750         316 :                 static_cast<swq_expr_node *>(m_poAttrQuery->GetSWQExpr());
    3751         316 :             poNode->ReplaceBetweenByGEAndLERecurse();
    3752         316 :             ExploreExprNode(poNode);
    3753         316 :             ComputeConstraintsArrayIdx();
    3754             :         }
    3755             :     }
    3756             : 
    3757         755 :     return OGRERR_NONE;
    3758             : }
    3759             : 
    3760             : /************************************************************************/
    3761             : /*                        ConstraintEvaluator()                         */
    3762             : /************************************************************************/
    3763             : 
    3764             : namespace
    3765             : {
    3766             : template <class T, class U> struct CompareGeneric
    3767             : {
    3768         289 :     static inline bool get(int op, const T &val1, const U &val2)
    3769             :     {
    3770         289 :         switch (op)
    3771             :         {
    3772           8 :             case SWQ_LE:
    3773           8 :                 return val1 <= val2;
    3774           8 :             case SWQ_LT:
    3775           8 :                 return val1 < val2;
    3776          90 :             case SWQ_NE:
    3777          90 :                 return val1 != val2;
    3778         163 :             case SWQ_EQ:
    3779         163 :                 return val1 == val2;
    3780          12 :             case SWQ_GE:
    3781          12 :                 return val1 >= val2;
    3782           8 :             case SWQ_GT:
    3783           8 :                 return val1 > val2;
    3784             :                 break;
    3785           0 :             default:
    3786           0 :                 CPLAssert(false);
    3787             :         }
    3788             :         return true;
    3789             :     }
    3790             : };
    3791             : 
    3792             : template <class T, class U> struct Compare
    3793             : {
    3794             : };
    3795             : 
    3796             : template <class T> struct Compare<T, T> : public CompareGeneric<T, T>
    3797             : {
    3798             : };
    3799             : 
    3800             : template <> struct Compare<int, GIntBig> : public CompareGeneric<int, GIntBig>
    3801             : {
    3802             : };
    3803             : 
    3804             : template <> struct Compare<double, GIntBig>
    3805             : {
    3806           0 :     static inline bool get(int op, double val1, GIntBig val2)
    3807             :     {
    3808           0 :         return CompareGeneric<double, double>::get(op, val1,
    3809           0 :                                                    static_cast<double>(val2));
    3810             :     }
    3811             : };
    3812             : 
    3813             : template <> struct Compare<GIntBig, int> : public CompareGeneric<GIntBig, int>
    3814             : {
    3815             : };
    3816             : 
    3817             : template <> struct Compare<double, int> : public CompareGeneric<double, int>
    3818             : {
    3819             : };
    3820             : 
    3821             : template <class T>
    3822         289 : static bool ConstraintEvaluator(const OGRArrowLayer::Constraint &constraint,
    3823             :                                 const T value)
    3824             : {
    3825         289 :     bool b = false;
    3826         289 :     switch (constraint.eType)
    3827             :     {
    3828         204 :         case OGRArrowLayer::Constraint::Type::Integer:
    3829         408 :             b = Compare<T, int>::get(constraint.nOperation, value,
    3830         204 :                                      constraint.sValue.Integer);
    3831         204 :             break;
    3832          43 :         case OGRArrowLayer::Constraint::Type::Integer64:
    3833          86 :             b = Compare<T, GIntBig>::get(constraint.nOperation, value,
    3834          43 :                                          constraint.sValue.Integer64);
    3835          43 :             break;
    3836          42 :         case OGRArrowLayer::Constraint::Type::Real:
    3837          84 :             b = Compare<double, double>::get(constraint.nOperation,
    3838           0 :                                              static_cast<double>(value),
    3839          42 :                                              constraint.sValue.Real);
    3840          42 :             break;
    3841           0 :         case OGRArrowLayer::Constraint::Type::String:
    3842           0 :             b = Compare<std::string, std::string>::get(constraint.nOperation,
    3843             :                                                        std::to_string(value),
    3844           0 :                                                        constraint.osValue);
    3845           0 :             break;
    3846             :     }
    3847         289 :     return b;
    3848             : }
    3849             : 
    3850          45 : inline bool CompareStr(int op, const std::string_view &val1,
    3851             :                        const std::string &val2)
    3852             : {
    3853          45 :     if (op == SWQ_EQ)
    3854             :     {
    3855          14 :         return val1 == val2;
    3856             :     }
    3857          31 :     const int cmpRes = val2.compare(val1);
    3858          31 :     switch (op)
    3859             :     {
    3860           4 :         case SWQ_LE:
    3861           4 :             return cmpRes >= 0;
    3862           4 :         case SWQ_LT:
    3863           4 :             return cmpRes > 0;
    3864          23 :         case SWQ_NE:
    3865          23 :             return cmpRes != 0;
    3866             :         // case SWQ_EQ: return cmpRes == 0;
    3867           0 :         case SWQ_GE:
    3868           0 :             return cmpRes <= 0;
    3869           0 :         case SWQ_GT:
    3870           0 :             return cmpRes < 0;
    3871             :             break;
    3872           0 :         default:
    3873           0 :             CPLAssert(false);
    3874             :     }
    3875             :     return true;
    3876             : }
    3877             : 
    3878          45 : inline bool ConstraintEvaluator(const OGRArrowLayer::Constraint &constraint,
    3879             :                                 const std::string_view &value)
    3880             : {
    3881          45 :     return CompareStr(constraint.nOperation, value, constraint.osValue);
    3882             : }
    3883             : 
    3884             : }  // namespace
    3885             : 
    3886             : /************************************************************************/
    3887             : /*               SkipToNextFeatureDueToAttributeFilter()                */
    3888             : /************************************************************************/
    3889             : 
    3890         448 : inline bool OGRArrowLayer::SkipToNextFeatureDueToAttributeFilter() const
    3891             : {
    3892         693 :     for (const auto &constraint : m_asAttributeFilterConstraints)
    3893             :     {
    3894         448 :         if (constraint.iArrayIdx < 0)
    3895             :         {
    3896          42 :             if (constraint.iField ==
    3897          34 :                     m_poFeatureDefn->GetFieldCount() + SPF_FID &&
    3898          16 :                 m_osFIDColumn.empty())
    3899             :             {
    3900          16 :                 if (!ConstraintEvaluator(constraint,
    3901          16 :                                          static_cast<GIntBig>(m_nFeatureIdx)))
    3902             :                 {
    3903         203 :                     return true;
    3904             :                 }
    3905           4 :                 continue;
    3906             :             }
    3907             :             else
    3908             :             {
    3909             :                 // can happen if ignoring a field that is needed by the
    3910             :                 // attribute filter. ComputeConstraintsArrayIdx() will have
    3911             :                 // warned about that
    3912           2 :                 continue;
    3913             :             }
    3914             :         }
    3915             : 
    3916             :         const arrow::Array *array =
    3917         430 :             m_poBatchColumns[constraint.iArrayIdx].get();
    3918             : 
    3919         430 :         const bool bIsNull = array->IsNull(m_nIdxInBatch);
    3920         430 :         if (constraint.nOperation == SWQ_ISNULL)
    3921             :         {
    3922           7 :             if (bIsNull)
    3923             :             {
    3924           3 :                 continue;
    3925             :             }
    3926           4 :             return true;
    3927             :         }
    3928         423 :         else if (constraint.nOperation == SWQ_ISNOTNULL)
    3929             :         {
    3930          22 :             if (!bIsNull)
    3931             :             {
    3932          18 :                 continue;
    3933             :             }
    3934           4 :             return true;
    3935             :         }
    3936         401 :         else if (bIsNull)
    3937             :         {
    3938          65 :             return true;
    3939             :         }
    3940             : 
    3941         336 :         switch (array->type_id())
    3942             :         {
    3943           0 :             case arrow::Type::NA:
    3944           0 :                 break;
    3945             : 
    3946          92 :             case arrow::Type::BOOL:
    3947             :             {
    3948          92 :                 const auto castArray =
    3949             :                     static_cast<const arrow::BooleanArray *>(array);
    3950          92 :                 if (!ConstraintEvaluator(
    3951             :                         constraint,
    3952          92 :                         static_cast<int>(castArray->Value(m_nIdxInBatch))))
    3953             :                 {
    3954          42 :                     return true;
    3955             :                 }
    3956          50 :                 break;
    3957             :             }
    3958           7 :             case arrow::Type::UINT8:
    3959             :             {
    3960           7 :                 const auto castArray =
    3961             :                     static_cast<const arrow::UInt8Array *>(array);
    3962           7 :                 if (!ConstraintEvaluator(
    3963             :                         constraint,
    3964           7 :                         static_cast<int>(castArray->Value(m_nIdxInBatch))))
    3965             :                 {
    3966           3 :                     return true;
    3967             :                 }
    3968           4 :                 break;
    3969             :             }
    3970          30 :             case arrow::Type::INT8:
    3971             :             {
    3972          30 :                 const auto castArray =
    3973             :                     static_cast<const arrow::Int8Array *>(array);
    3974          30 :                 if (!ConstraintEvaluator(
    3975             :                         constraint,
    3976          30 :                         static_cast<int>(castArray->Value(m_nIdxInBatch))))
    3977             :                 {
    3978           2 :                     return true;
    3979             :                 }
    3980          28 :                 break;
    3981             :             }
    3982           4 :             case arrow::Type::UINT16:
    3983             :             {
    3984           4 :                 const auto castArray =
    3985             :                     static_cast<const arrow::UInt16Array *>(array);
    3986           4 :                 if (!ConstraintEvaluator(
    3987             :                         constraint,
    3988           4 :                         static_cast<int>(castArray->Value(m_nIdxInBatch))))
    3989             :                 {
    3990           2 :                     return true;
    3991             :                 }
    3992           2 :                 break;
    3993             :             }
    3994           0 :             case arrow::Type::INT16:
    3995             :             {
    3996           0 :                 const auto castArray =
    3997             :                     static_cast<const arrow::Int16Array *>(array);
    3998           0 :                 if (!ConstraintEvaluator(
    3999             :                         constraint,
    4000           0 :                         static_cast<int>(castArray->Value(m_nIdxInBatch))))
    4001             :                 {
    4002           0 :                     return true;
    4003             :                 }
    4004           0 :                 break;
    4005             :             }
    4006           0 :             case arrow::Type::UINT32:
    4007             :             {
    4008           0 :                 const auto castArray =
    4009             :                     static_cast<const arrow::UInt32Array *>(array);
    4010           0 :                 if (!ConstraintEvaluator(
    4011             :                         constraint,
    4012           0 :                         static_cast<GIntBig>(castArray->Value(m_nIdxInBatch))))
    4013             :                 {
    4014           0 :                     return true;
    4015             :                 }
    4016           0 :                 break;
    4017             :             }
    4018          71 :             case arrow::Type::INT32:
    4019             :             {
    4020          71 :                 const auto castArray =
    4021             :                     static_cast<const arrow::Int32Array *>(array);
    4022          71 :                 if (!ConstraintEvaluator(constraint,
    4023          71 :                                          castArray->Value(m_nIdxInBatch)))
    4024             :                 {
    4025          28 :                     return true;
    4026             :                 }
    4027          43 :                 break;
    4028             :             }
    4029           8 :             case arrow::Type::UINT64:
    4030             :             {
    4031           8 :                 const auto castArray =
    4032             :                     static_cast<const arrow::UInt64Array *>(array);
    4033           8 :                 if (!ConstraintEvaluator(
    4034             :                         constraint,
    4035           8 :                         static_cast<double>(castArray->Value(m_nIdxInBatch))))
    4036             :                 {
    4037           6 :                     return true;
    4038             :                 }
    4039           2 :                 break;
    4040             :             }
    4041          27 :             case arrow::Type::INT64:
    4042             :             {
    4043          27 :                 const auto castArray =
    4044             :                     static_cast<const arrow::Int64Array *>(array);
    4045          27 :                 if (!ConstraintEvaluator(
    4046             :                         constraint,
    4047          27 :                         static_cast<GIntBig>(castArray->Value(m_nIdxInBatch))))
    4048             :                 {
    4049          10 :                     return true;
    4050             :                 }
    4051          17 :                 break;
    4052             :             }
    4053           0 :             case arrow::Type::HALF_FLOAT:
    4054             :             {
    4055           0 :                 const auto castArray =
    4056             :                     static_cast<const arrow::HalfFloatArray *>(array);
    4057           0 :                 const uint16_t nFloat16 = castArray->Value(m_nIdxInBatch);
    4058           0 :                 uint32_t nFloat32 = CPLHalfToFloat(nFloat16);
    4059             :                 float f;
    4060           0 :                 memcpy(&f, &nFloat32, sizeof(nFloat32));
    4061           0 :                 if (!ConstraintEvaluator(constraint, static_cast<double>(f)))
    4062             :                 {
    4063           0 :                     return true;
    4064             :                 }
    4065           0 :                 break;
    4066             :             }
    4067           4 :             case arrow::Type::FLOAT:
    4068             :             {
    4069           4 :                 const auto castArray =
    4070             :                     static_cast<const arrow::FloatArray *>(array);
    4071           4 :                 if (!ConstraintEvaluator(
    4072             :                         constraint,
    4073           4 :                         static_cast<double>(castArray->Value(m_nIdxInBatch))))
    4074             :                 {
    4075           2 :                     return true;
    4076             :                 }
    4077           2 :                 break;
    4078             :             }
    4079          22 :             case arrow::Type::DOUBLE:
    4080             :             {
    4081          22 :                 const auto castArray =
    4082             :                     static_cast<const arrow::DoubleArray *>(array);
    4083          22 :                 if (!ConstraintEvaluator(constraint,
    4084          22 :                                          castArray->Value(m_nIdxInBatch)))
    4085             :                 {
    4086           6 :                     return true;
    4087             :                 }
    4088          16 :                 break;
    4089             :             }
    4090          45 :             case arrow::Type::STRING:
    4091             :             {
    4092          45 :                 const auto castArray =
    4093             :                     static_cast<const arrow::StringArray *>(array);
    4094          45 :                 int out_length = 0;
    4095             :                 const uint8_t *data =
    4096          45 :                     castArray->GetValue(m_nIdxInBatch, &out_length);
    4097          45 :                 if (!ConstraintEvaluator(
    4098             :                         constraint,
    4099          90 :                         std::string_view(reinterpret_cast<const char *>(data),
    4100             :                                          out_length)))
    4101             :                 {
    4102          13 :                     return true;
    4103             :                 }
    4104          32 :                 break;
    4105             :             }
    4106             : 
    4107             : #if ARROW_VERSION_MAJOR >= 18
    4108           0 :             case arrow::Type::DECIMAL32:
    4109             :             {
    4110           0 :                 const auto castArray =
    4111             :                     static_cast<const arrow::Decimal32Array *>(array);
    4112           0 :                 if (!ConstraintEvaluator(
    4113             :                         constraint,
    4114           0 :                         CPLAtof(castArray->FormatValue(m_nIdxInBatch).c_str())))
    4115             :                 {
    4116           0 :                     return true;
    4117             :                 }
    4118           0 :                 break;
    4119             :             }
    4120             : 
    4121           0 :             case arrow::Type::DECIMAL64:
    4122             :             {
    4123           0 :                 const auto castArray =
    4124             :                     static_cast<const arrow::Decimal64Array *>(array);
    4125           0 :                 if (!ConstraintEvaluator(
    4126             :                         constraint,
    4127           0 :                         CPLAtof(castArray->FormatValue(m_nIdxInBatch).c_str())))
    4128             :                 {
    4129           0 :                     return true;
    4130             :                 }
    4131           0 :                 break;
    4132             :             }
    4133             : #endif
    4134             : 
    4135           4 :             case arrow::Type::DECIMAL128:
    4136             :             {
    4137           4 :                 const auto castArray =
    4138             :                     static_cast<const arrow::Decimal128Array *>(array);
    4139           4 :                 if (!ConstraintEvaluator(
    4140             :                         constraint,
    4141           8 :                         CPLAtof(castArray->FormatValue(m_nIdxInBatch).c_str())))
    4142             :                 {
    4143           2 :                     return true;
    4144             :                 }
    4145           2 :                 break;
    4146             :             }
    4147             : 
    4148           4 :             case arrow::Type::DECIMAL256:
    4149             :             {
    4150           4 :                 const auto castArray =
    4151             :                     static_cast<const arrow::Decimal256Array *>(array);
    4152           4 :                 if (!ConstraintEvaluator(
    4153             :                         constraint,
    4154           8 :                         CPLAtof(castArray->FormatValue(m_nIdxInBatch).c_str())))
    4155             :                 {
    4156           2 :                     return true;
    4157             :                 }
    4158           2 :                 break;
    4159             :             }
    4160             : 
    4161          18 :             default:
    4162          18 :                 break;
    4163             :         }
    4164             :     }
    4165         245 :     return false;
    4166             : }
    4167             : 
    4168             : /************************************************************************/
    4169             : /*                              SetBatch()                              */
    4170             : /************************************************************************/
    4171             : 
    4172             : inline void
    4173        4344 : OGRArrowLayer::SetBatch(const std::shared_ptr<arrow::RecordBatch> &poBatch)
    4174             : {
    4175        4344 :     m_poBatch = poBatch;
    4176        4344 :     m_poBatchColumns.clear();
    4177        4344 :     m_poArrayWKB = nullptr;
    4178        4344 :     m_poArrayWKBLarge = nullptr;
    4179        4344 :     m_poArrayBBOX = nullptr;
    4180        4344 :     m_poArrayXMinDouble = nullptr;
    4181        4344 :     m_poArrayYMinDouble = nullptr;
    4182        4344 :     m_poArrayXMaxDouble = nullptr;
    4183        4344 :     m_poArrayYMaxDouble = nullptr;
    4184        4344 :     m_poArrayXMinFloat = nullptr;
    4185        4344 :     m_poArrayYMinFloat = nullptr;
    4186        4344 :     m_poArrayXMaxFloat = nullptr;
    4187        4344 :     m_poArrayYMaxFloat = nullptr;
    4188             : 
    4189        4344 :     if (m_poBatch)
    4190             :     {
    4191        2996 :         m_poBatchColumns = m_poBatch->columns();
    4192        2996 :         SanityCheckOfSetBatch();
    4193             :     }
    4194             : 
    4195        4344 :     if (m_poBatch && m_poFilterGeom && !m_bBaseArrowIgnoreSpatialFilterRect)
    4196             :     {
    4197             :         int iCol;
    4198         672 :         if (m_bIgnoredFields)
    4199             :         {
    4200         204 :             iCol = m_anMapGeomFieldIndexToArrayIndex[m_iGeomFieldFilter];
    4201             :         }
    4202             :         else
    4203             :         {
    4204         468 :             iCol = m_anMapGeomFieldIndexToArrowColumn[m_iGeomFieldFilter];
    4205             :         }
    4206        1344 :         if (iCol >= 0 &&
    4207         672 :             m_aeGeomEncoding[m_iGeomFieldFilter] == OGRArrowGeomEncoding::WKB)
    4208             :         {
    4209             :             const arrow::Array *poArrayWKB =
    4210         117 :                 GetStorageArray(m_poBatchColumns[iCol].get());
    4211         117 :             if (poArrayWKB->type_id() == arrow::Type::BINARY)
    4212         117 :                 m_poArrayWKB =
    4213             :                     static_cast<const arrow::BinaryArray *>(poArrayWKB);
    4214             :             else
    4215             :             {
    4216           0 :                 CPLAssert(poArrayWKB->type_id() == arrow::Type::LARGE_BINARY);
    4217           0 :                 m_poArrayWKBLarge =
    4218             :                     static_cast<const arrow::LargeBinaryArray *>(poArrayWKB);
    4219             :             }
    4220             :         }
    4221             : 
    4222        1344 :         if (iCol >= 0 &&
    4223         672 :             CPLTestBool(CPLGetConfigOption(
    4224        1344 :                 ("OGR_" + GetDriverUCName() + "_USE_BBOX").c_str(), "YES")))
    4225             :         {
    4226             :             const auto oIter =
    4227         672 :                 m_oMapGeomFieldIndexToGeomColBBOX.find(m_iGeomFieldFilter);
    4228         672 :             if (oIter != m_oMapGeomFieldIndexToGeomColBBOX.end())
    4229             :             {
    4230         422 :                 const int idx = m_bIgnoredFields ? oIter->second.iArrayIdx
    4231         155 :                                                  : oIter->second.iArrowCol;
    4232         267 :                 if (idx >= 0)
    4233             :                 {
    4234         211 :                     CPLAssert(static_cast<size_t>(idx) <
    4235             :                               m_poBatchColumns.size());
    4236         211 :                     m_poArrayBBOX = m_poBatchColumns[idx].get();
    4237         211 :                     CPLAssert(m_poArrayBBOX->type_id() == arrow::Type::STRUCT);
    4238         211 :                     const auto castArray =
    4239             :                         static_cast<const arrow::StructArray *>(m_poArrayBBOX);
    4240         211 :                     const auto &subArrays = castArray->fields();
    4241         211 :                     CPLAssert(
    4242             :                         static_cast<size_t>(oIter->second.iArrowSubfieldXMin) <
    4243             :                         subArrays.size());
    4244             :                     const auto xminArray =
    4245         211 :                         subArrays[oIter->second.iArrowSubfieldXMin].get();
    4246         211 :                     CPLAssert(
    4247             :                         static_cast<size_t>(oIter->second.iArrowSubfieldYMin) <
    4248             :                         subArrays.size());
    4249             :                     const auto yminArray =
    4250         211 :                         subArrays[oIter->second.iArrowSubfieldYMin].get();
    4251         211 :                     CPLAssert(
    4252             :                         static_cast<size_t>(oIter->second.iArrowSubfieldXMax) <
    4253             :                         subArrays.size());
    4254             :                     const auto xmaxArray =
    4255         211 :                         subArrays[oIter->second.iArrowSubfieldXMax].get();
    4256         211 :                     CPLAssert(
    4257             :                         static_cast<size_t>(oIter->second.iArrowSubfieldYMax) <
    4258             :                         subArrays.size());
    4259             :                     const auto ymaxArray =
    4260         211 :                         subArrays[oIter->second.iArrowSubfieldYMax].get();
    4261         211 :                     if (oIter->second.bIsFloat)
    4262             :                     {
    4263         210 :                         CPLAssert(xminArray->type_id() == arrow::Type::FLOAT);
    4264         210 :                         m_poArrayXMinFloat =
    4265             :                             static_cast<const arrow::FloatArray *>(xminArray);
    4266         210 :                         CPLAssert(yminArray->type_id() == arrow::Type::FLOAT);
    4267         210 :                         m_poArrayYMinFloat =
    4268             :                             static_cast<const arrow::FloatArray *>(yminArray);
    4269         210 :                         CPLAssert(xmaxArray->type_id() == arrow::Type::FLOAT);
    4270         210 :                         m_poArrayXMaxFloat =
    4271             :                             static_cast<const arrow::FloatArray *>(xmaxArray);
    4272         210 :                         CPLAssert(ymaxArray->type_id() == arrow::Type::FLOAT);
    4273         210 :                         m_poArrayYMaxFloat =
    4274             :                             static_cast<const arrow::FloatArray *>(ymaxArray);
    4275             :                     }
    4276             :                     else
    4277             :                     {
    4278           1 :                         CPLAssert(xminArray->type_id() == arrow::Type::DOUBLE);
    4279           1 :                         m_poArrayXMinDouble =
    4280             :                             static_cast<const arrow::DoubleArray *>(xminArray);
    4281           1 :                         CPLAssert(yminArray->type_id() == arrow::Type::DOUBLE);
    4282           1 :                         m_poArrayYMinDouble =
    4283             :                             static_cast<const arrow::DoubleArray *>(yminArray);
    4284           1 :                         CPLAssert(xmaxArray->type_id() == arrow::Type::DOUBLE);
    4285           1 :                         m_poArrayXMaxDouble =
    4286             :                             static_cast<const arrow::DoubleArray *>(xmaxArray);
    4287           1 :                         CPLAssert(ymaxArray->type_id() == arrow::Type::DOUBLE);
    4288           1 :                         m_poArrayYMaxDouble =
    4289             :                             static_cast<const arrow::DoubleArray *>(ymaxArray);
    4290             :                     }
    4291             :                 }
    4292             :             }
    4293             :         }
    4294             :     }
    4295        4344 : }
    4296             : 
    4297             : /************************************************************************/
    4298             : /*                       SanityCheckOfSetBatch()                        */
    4299             : /************************************************************************/
    4300             : 
    4301        2996 : inline void OGRArrowLayer::SanityCheckOfSetBatch() const
    4302             : {
    4303             : #ifdef DEBUG
    4304        2996 :     CPLAssert(m_poBatch);
    4305             : 
    4306        2996 :     const auto &poColumns = m_poBatch->columns();
    4307             : 
    4308             :     // Sanity checks
    4309        2996 :     CPLAssert(m_poBatch->num_columns() == (m_bIgnoredFields
    4310             :                                                ? m_nExpectedBatchColumns
    4311             :                                                : m_poSchema->num_fields()));
    4312        2996 :     const auto &fields = m_poSchema->fields();
    4313             : 
    4314       96083 :     for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
    4315             :     {
    4316             :         int iCol;
    4317       93087 :         if (m_bIgnoredFields)
    4318             :         {
    4319       15253 :             iCol = m_anMapFieldIndexToArrayIndex[i];
    4320       15253 :             if (iCol < 0)
    4321        3894 :                 continue;
    4322             :         }
    4323             :         else
    4324             :         {
    4325       77834 :             iCol = m_anMapFieldIndexToArrowColumn[i][0];
    4326             :         }
    4327       89193 :         CPL_IGNORE_RET_VAL(iCol);  // to make cppcheck happy
    4328             : 
    4329       89193 :         CPLAssert(iCol < static_cast<int>(poColumns.size()));
    4330       89193 :         CPLAssert(fields[m_anMapFieldIndexToArrowColumn[i][0]]->type()->id() ==
    4331             :                   poColumns[iCol]->type_id());
    4332             :     }
    4333             : 
    4334        5880 :     for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i)
    4335             :     {
    4336             :         int iCol;
    4337        2884 :         if (m_bIgnoredFields)
    4338             :         {
    4339         653 :             iCol = m_anMapGeomFieldIndexToArrayIndex[i];
    4340         653 :             if (iCol < 0)
    4341          30 :                 continue;
    4342             :         }
    4343             :         else
    4344             :         {
    4345        2231 :             iCol = m_anMapGeomFieldIndexToArrowColumn[i];
    4346             :         }
    4347        2854 :         CPL_IGNORE_RET_VAL(iCol);  // to make cppcheck happy
    4348             : 
    4349        2854 :         CPLAssert(iCol < static_cast<int>(poColumns.size()));
    4350        2854 :         CPLAssert(fields[m_anMapGeomFieldIndexToArrowColumn[i]]->type()->id() ==
    4351             :                   poColumns[iCol]->type_id());
    4352             :     }
    4353             : #else
    4354             :     CPL_IGNORE_RET_VAL(m_nExpectedBatchColumns);
    4355             : #endif
    4356        2996 : }
    4357             : 
    4358             : /************************************************************************/
    4359             : /*                         GetNextRawFeature()                          */
    4360             : /************************************************************************/
    4361             : 
    4362       13280 : inline OGRFeature *OGRArrowLayer::GetNextRawFeature()
    4363             : {
    4364       13280 :     if (m_bEOF || !m_bSpatialFilterIntersectsLayerExtent)
    4365         533 :         return nullptr;
    4366             : 
    4367       12747 :     if (m_poBatch == nullptr || m_nIdxInBatch == m_poBatch->num_rows())
    4368             :     {
    4369        4028 :         m_bEOF = !ReadNextBatch();
    4370        4028 :         if (m_bEOF)
    4371        1686 :             return nullptr;
    4372             :     }
    4373             : 
    4374             :     // Evaluate spatial filter by computing the bounding box of each geometry
    4375             :     // but without creating a OGRGeometry
    4376       11061 :     if (m_poFilterGeom && !m_bBaseArrowIgnoreSpatialFilterRect)
    4377             :     {
    4378             :         int iCol;
    4379        1167 :         if (m_bIgnoredFields)
    4380             :         {
    4381         276 :             iCol = m_anMapGeomFieldIndexToArrayIndex[m_iGeomFieldFilter];
    4382             :         }
    4383             :         else
    4384             :         {
    4385         891 :             iCol = m_anMapGeomFieldIndexToArrowColumn[m_iGeomFieldFilter];
    4386             :         }
    4387             : 
    4388        1167 :         if (iCol >= 0 && (m_poArrayXMinFloat || m_poArrayXMinDouble))
    4389             :         {
    4390         463 :             OGREnvelope sEnvelopeSkipToNextFeatureDueToBBOX;
    4391             :             const auto IntersectsBBOX =
    4392        3459 :                 [this, &sEnvelopeSkipToNextFeatureDueToBBOX]()
    4393             :             {
    4394         987 :                 if (m_poArrayXMinFloat &&
    4395         493 :                     !m_poArrayXMinFloat->IsNull(m_nIdxInBatch))
    4396             :                 {
    4397         493 :                     sEnvelopeSkipToNextFeatureDueToBBOX.MinX =
    4398         493 :                         m_poArrayXMinFloat->Value(m_nIdxInBatch);
    4399         493 :                     sEnvelopeSkipToNextFeatureDueToBBOX.MinY =
    4400         493 :                         m_poArrayYMinFloat->Value(m_nIdxInBatch);
    4401         493 :                     sEnvelopeSkipToNextFeatureDueToBBOX.MaxX =
    4402         493 :                         m_poArrayXMaxFloat->Value(m_nIdxInBatch);
    4403         493 :                     sEnvelopeSkipToNextFeatureDueToBBOX.MaxY =
    4404         493 :                         m_poArrayYMaxFloat->Value(m_nIdxInBatch);
    4405         493 :                     if (m_sFilterEnvelope.Intersects(
    4406         493 :                             sEnvelopeSkipToNextFeatureDueToBBOX))
    4407             :                     {
    4408         450 :                         return true;
    4409             :                     }
    4410             :                 }
    4411           2 :                 else if (m_poArrayXMinDouble &&
    4412           1 :                          !m_poArrayXMinDouble->IsNull(m_nIdxInBatch))
    4413             :                 {
    4414           1 :                     sEnvelopeSkipToNextFeatureDueToBBOX.MinX =
    4415           1 :                         m_poArrayXMinDouble->Value(m_nIdxInBatch);
    4416           1 :                     sEnvelopeSkipToNextFeatureDueToBBOX.MinY =
    4417           1 :                         m_poArrayYMinDouble->Value(m_nIdxInBatch);
    4418           1 :                     sEnvelopeSkipToNextFeatureDueToBBOX.MaxX =
    4419           1 :                         m_poArrayXMaxDouble->Value(m_nIdxInBatch);
    4420           1 :                     sEnvelopeSkipToNextFeatureDueToBBOX.MaxY =
    4421           1 :                         m_poArrayYMaxDouble->Value(m_nIdxInBatch);
    4422           1 :                     if (m_sFilterEnvelope.Intersects(
    4423           1 :                             sEnvelopeSkipToNextFeatureDueToBBOX))
    4424             :                     {
    4425           1 :                         return true;
    4426             :                     }
    4427             :                 }
    4428          43 :                 return false;
    4429         463 :             };
    4430             : 
    4431             :             while (true)
    4432             :             {
    4433        1321 :                 if (!m_poArrayBBOX->IsNull(m_nIdxInBatch) && IntersectsBBOX() &&
    4434         451 :                     (m_asAttributeFilterConstraints.empty() ||
    4435           2 :                      !SkipToNextFeatureDueToAttributeFilter()))
    4436             :                 {
    4437         450 :                     break;
    4438             :                 }
    4439             : 
    4440         418 :                 IncrFeatureIdx();
    4441         418 :                 m_nIdxInBatch++;
    4442         418 :                 if (m_nIdxInBatch == m_poBatch->num_rows())
    4443             :                 {
    4444          22 :                     m_bEOF = !ReadNextBatch();
    4445          22 :                     if (m_bEOF)
    4446          13 :                         return nullptr;
    4447             :                 }
    4448         450 :             }
    4449             :         }
    4450         704 :         else if (iCol >= 0 && m_aeGeomEncoding[m_iGeomFieldFilter] ==
    4451             :                                   OGRArrowGeomEncoding::WKB)
    4452             :         {
    4453         131 :             CPLAssert(m_poArrayWKB || m_poArrayWKBLarge);
    4454         131 :             OGREnvelope sEnvelope;
    4455             : 
    4456             :             while (true)
    4457             :             {
    4458         220 :                 bool bMatchBBOX = false;
    4459         417 :                 if ((m_poArrayWKB && m_poArrayWKB->IsNull(m_nIdxInBatch)) ||
    4460         197 :                     (m_poArrayWKBLarge &&
    4461           0 :                      m_poArrayWKBLarge->IsNull(m_nIdxInBatch)))
    4462             :                 {
    4463             :                     // nothing to do
    4464             :                 }
    4465             :                 else
    4466             :                 {
    4467         197 :                     if (m_poArrayWKB)
    4468             :                     {
    4469         197 :                         int out_length = 0;
    4470             :                         const uint8_t *data =
    4471         197 :                             m_poArrayWKB->GetValue(m_nIdxInBatch, &out_length);
    4472         394 :                         if (OGRWKBGetBoundingBox(data, out_length, sEnvelope) &&
    4473         197 :                             m_sFilterEnvelope.Intersects(sEnvelope))
    4474             :                         {
    4475         115 :                             bMatchBBOX = true;
    4476             :                         }
    4477             :                     }
    4478             :                     else
    4479             :                     {
    4480           0 :                         CPLAssert(m_poArrayWKBLarge);
    4481           0 :                         int64_t out_length64 = 0;
    4482           0 :                         const uint8_t *data = m_poArrayWKBLarge->GetValue(
    4483             :                             m_nIdxInBatch, &out_length64);
    4484           0 :                         if (out_length64 < INT_MAX &&
    4485           0 :                             OGRWKBGetBoundingBox(data,
    4486             :                                                  static_cast<int>(out_length64),
    4487           0 :                                                  sEnvelope) &&
    4488           0 :                             m_sFilterEnvelope.Intersects(sEnvelope))
    4489             :                         {
    4490           0 :                             bMatchBBOX = true;
    4491             :                         }
    4492             :                     }
    4493             :                 }
    4494         221 :                 if (bMatchBBOX && (m_asAttributeFilterConstraints.empty() ||
    4495           1 :                                    !SkipToNextFeatureDueToAttributeFilter()))
    4496             :                 {
    4497         115 :                     break;
    4498             :                 }
    4499             : 
    4500         105 :                 IncrFeatureIdx();
    4501         105 :                 m_nIdxInBatch++;
    4502         105 :                 if (m_nIdxInBatch == m_poBatch->num_rows())
    4503             :                 {
    4504          25 :                     m_bEOF = !ReadNextBatch();
    4505          25 :                     if (m_bEOF)
    4506          16 :                         return nullptr;
    4507             :                 }
    4508          89 :             }
    4509             :         }
    4510        1146 :         else if (iCol >= 0 &&
    4511         573 :                  m_aeGeomEncoding[m_iGeomFieldFilter] ==
    4512             :                      OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOLYGON)
    4513             :         {
    4514             :             const auto poGeomFieldDefn =
    4515          10 :                 m_poFeatureDefn->GetGeomFieldDefn(m_iGeomFieldFilter);
    4516          10 :             const auto eGeomType = poGeomFieldDefn->GetType();
    4517          10 :             const bool bHasZ = CPL_TO_BOOL(OGR_GT_HasZ(eGeomType));
    4518          10 :             const bool bHasM = CPL_TO_BOOL(OGR_GT_HasM(eGeomType));
    4519          10 :             const int nDim = 2 + (bHasZ ? 1 : 0) + (bHasM ? 1 : 0);
    4520             : 
    4521             :             bool bReturnFeature;
    4522           0 :             do
    4523             :             {
    4524          10 :                 bReturnFeature = false;
    4525          10 :                 auto array = GetStorageArray(m_poBatchColumns[iCol].get());
    4526          10 :                 CPLAssert(array->type_id() == arrow::Type::LIST);
    4527          10 :                 auto listOfPartsArray =
    4528             :                     static_cast<const arrow::ListArray *>(array);
    4529          10 :                 CPLAssert(listOfPartsArray->values()->type_id() ==
    4530             :                           arrow::Type::LIST);
    4531             :                 auto listOfPartsValues =
    4532             :                     std::static_pointer_cast<arrow::ListArray>(
    4533          10 :                         listOfPartsArray->values());
    4534          10 :                 CPLAssert(listOfPartsValues->values()->type_id() ==
    4535             :                           arrow::Type::LIST);
    4536             :                 auto listOfRingsValues =
    4537             :                     std::static_pointer_cast<arrow::ListArray>(
    4538          10 :                         listOfPartsValues->values());
    4539          10 :                 CPLAssert(listOfRingsValues->values()->type_id() ==
    4540             :                           arrow::Type::FIXED_SIZE_LIST);
    4541             :                 auto listOfPointsValues =
    4542             :                     std::static_pointer_cast<arrow::FixedSizeListArray>(
    4543          10 :                         listOfRingsValues->values());
    4544          10 :                 CPLAssert(listOfPointsValues->values()->type_id() ==
    4545             :                           arrow::Type::DOUBLE);
    4546             :                 auto pointValues = std::static_pointer_cast<arrow::DoubleArray>(
    4547          10 :                     listOfPointsValues->values());
    4548             : 
    4549             :                 while (true)
    4550             :                 {
    4551          18 :                     bool bMatchBBOX = false;
    4552          18 :                     if (!listOfPartsArray->IsNull(m_nIdxInBatch))
    4553             :                     {
    4554          14 :                         OGREnvelope sEnvelope;
    4555             :                         const auto nParts =
    4556          14 :                             listOfPartsArray->value_length(m_nIdxInBatch);
    4557             :                         const auto nPartOffset =
    4558          14 :                             listOfPartsArray->value_offset(m_nIdxInBatch);
    4559          28 :                         for (auto j = decltype(nParts){0}; j < nParts; j++)
    4560             :                         {
    4561          14 :                             const auto nRings = listOfPartsValues->value_length(
    4562          14 :                                 nPartOffset + j);
    4563             :                             const auto nRingOffset =
    4564          14 :                                 listOfPartsValues->value_offset(nPartOffset +
    4565             :                                                                 j);
    4566          14 :                             if (nRings >= 1)
    4567             :                             {
    4568             :                                 const auto nPoints =
    4569          14 :                                     listOfRingsValues->value_length(
    4570             :                                         nRingOffset);
    4571             :                                 const auto nPointOffset =
    4572          14 :                                     listOfRingsValues->value_offset(
    4573             :                                         nRingOffset) *
    4574          14 :                                     nDim;
    4575             :                                 const double *padfRawValue =
    4576          14 :                                     pointValues->raw_values() + nPointOffset;
    4577          73 :                                 for (auto l = decltype(nPoints){0}; l < nPoints;
    4578             :                                      ++l)
    4579             :                                 {
    4580          59 :                                     sEnvelope.Merge(padfRawValue[nDim * l],
    4581          59 :                                                     padfRawValue[nDim * l + 1]);
    4582             :                                 }
    4583             :                                 // for bounding box, only the first ring matters
    4584             :                             }
    4585             :                         }
    4586             : 
    4587          24 :                         if (nParts != 0 &&
    4588          10 :                             m_sFilterEnvelope.Intersects(sEnvelope))
    4589             :                         {
    4590          10 :                             bMatchBBOX = true;
    4591             :                         }
    4592             :                     }
    4593          28 :                     if (bMatchBBOX &&
    4594          10 :                         (m_asAttributeFilterConstraints.empty() ||
    4595           0 :                          !SkipToNextFeatureDueToAttributeFilter()))
    4596             :                     {
    4597          10 :                         bReturnFeature = true;
    4598          10 :                         break;
    4599             :                     }
    4600             : 
    4601           8 :                     IncrFeatureIdx();
    4602           8 :                     m_nIdxInBatch++;
    4603           8 :                     if (m_nIdxInBatch == m_poBatch->num_rows())
    4604             :                     {
    4605           0 :                         m_bEOF = !ReadNextBatch();
    4606           0 :                         if (m_bEOF)
    4607           0 :                             return nullptr;
    4608           0 :                         break;
    4609             :                     }
    4610           8 :                 }
    4611          10 :             } while (!bReturnFeature);
    4612             :         }
    4613         563 :         else if (iCol >= 0 && m_aeGeomEncoding[m_iGeomFieldFilter] ==
    4614             :                                   OGRArrowGeomEncoding::GEOARROW_STRUCT_POINT)
    4615             :         {
    4616             :             bool bReturnFeature;
    4617          10 :             do
    4618             :             {
    4619          44 :                 bReturnFeature = false;
    4620          44 :                 auto array = GetStorageArray(m_poBatchColumns[iCol].get());
    4621          44 :                 CPLAssert(array->type_id() == arrow::Type::STRUCT);
    4622          44 :                 auto pointValues =
    4623             :                     static_cast<const arrow::StructArray *>(array);
    4624          44 :                 const auto &fields = pointValues->fields();
    4625          44 :                 const auto &fieldX = fields[0];
    4626          44 :                 CPLAssert(fieldX->type_id() == arrow::Type::DOUBLE);
    4627             :                 const auto fieldXDouble =
    4628          44 :                     static_cast<arrow::DoubleArray *>(fieldX.get());
    4629          44 :                 const auto &fieldY = fields[1];
    4630          44 :                 CPLAssert(fieldY->type_id() == arrow::Type::DOUBLE);
    4631             :                 const auto fieldYDouble =
    4632          44 :                     static_cast<arrow::DoubleArray *>(fieldY.get());
    4633             : 
    4634             :                 while (true)
    4635             :                 {
    4636          98 :                     bool bMatchBBOX = false;
    4637          98 :                     if (!array->IsNull(m_nIdxInBatch))
    4638             :                     {
    4639          76 :                         const double dfX = fieldXDouble->Value(m_nIdxInBatch);
    4640          76 :                         const double dfY = fieldYDouble->Value(m_nIdxInBatch);
    4641          76 :                         if (dfX >= m_sFilterEnvelope.MinX &&
    4642          46 :                             dfY >= m_sFilterEnvelope.MinY &&
    4643          46 :                             dfX <= m_sFilterEnvelope.MaxX &&
    4644          30 :                             dfY <= m_sFilterEnvelope.MaxY)
    4645             :                         {
    4646          30 :                             bMatchBBOX = true;
    4647             :                         }
    4648             :                     }
    4649         128 :                     if (bMatchBBOX &&
    4650          30 :                         (m_asAttributeFilterConstraints.empty() ||
    4651           0 :                          !SkipToNextFeatureDueToAttributeFilter()))
    4652             :                     {
    4653          30 :                         bReturnFeature = true;
    4654          30 :                         break;
    4655             :                     }
    4656             : 
    4657          68 :                     IncrFeatureIdx();
    4658          68 :                     m_nIdxInBatch++;
    4659          68 :                     if (m_nIdxInBatch == m_poBatch->num_rows())
    4660             :                     {
    4661          14 :                         m_bEOF = !ReadNextBatch();
    4662          14 :                         if (m_bEOF)
    4663           4 :                             return nullptr;
    4664          10 :                         break;
    4665             :                     }
    4666          54 :                 }
    4667          40 :             } while (!bReturnFeature);
    4668             :         }
    4669        1058 :         else if (iCol >= 0 &&
    4670         529 :                  m_aeGeomEncoding[m_iGeomFieldFilter] ==
    4671             :                      OGRArrowGeomEncoding::GEOARROW_STRUCT_LINESTRING)
    4672             :         {
    4673             :             bool bReturnFeature;
    4674           0 :             do
    4675             :             {
    4676          64 :                 bReturnFeature = false;
    4677          64 :                 auto array = GetStorageArray(m_poBatchColumns[iCol].get());
    4678          64 :                 CPLAssert(array->type_id() == arrow::Type::LIST);
    4679          64 :                 const auto listArray =
    4680             :                     static_cast<const arrow::ListArray *>(array);
    4681          64 :                 CPLAssert(listArray->values()->type_id() ==
    4682             :                           arrow::Type::STRUCT);
    4683             :                 auto pointValues = std::static_pointer_cast<arrow::StructArray>(
    4684          64 :                     listArray->values());
    4685          64 :                 const auto &fields = pointValues->fields();
    4686          64 :                 const auto &fieldX = fields[0];
    4687          64 :                 CPLAssert(fieldX->type_id() == arrow::Type::DOUBLE);
    4688             :                 const auto fieldXDouble =
    4689          64 :                     static_cast<arrow::DoubleArray *>(fieldX.get());
    4690          64 :                 const auto &fieldY = fields[1];
    4691          64 :                 CPLAssert(fieldY->type_id() == arrow::Type::DOUBLE);
    4692             :                 const auto fieldYDouble =
    4693          64 :                     static_cast<arrow::DoubleArray *>(fieldY.get());
    4694             : 
    4695             :                 while (true)
    4696             :                 {
    4697         160 :                     bool bMatchBBOX = false;
    4698         160 :                     if (!listArray->IsNull(m_nIdxInBatch))
    4699             :                     {
    4700         120 :                         OGREnvelope sEnvelope;
    4701             :                         const auto nPoints =
    4702         120 :                             listArray->value_length(m_nIdxInBatch);
    4703             :                         const auto nPointOffset =
    4704         120 :                             listArray->value_offset(m_nIdxInBatch);
    4705         120 :                         if (nPoints > 0)
    4706             :                         {
    4707             :                             const double *padfRawXValue =
    4708          80 :                                 fieldXDouble->raw_values() + nPointOffset;
    4709             :                             const double *padfRawYValue =
    4710          80 :                                 fieldYDouble->raw_values() + nPointOffset;
    4711         240 :                             for (auto l = decltype(nPoints){0}; l < nPoints;
    4712             :                                  ++l)
    4713             :                             {
    4714         160 :                                 sEnvelope.Merge(padfRawXValue[l],
    4715         160 :                                                 padfRawYValue[l]);
    4716             :                             }
    4717          80 :                             if (m_sFilterEnvelope.Intersects(sEnvelope))
    4718             :                             {
    4719          48 :                                 bMatchBBOX = true;
    4720             :                             }
    4721             :                         }
    4722             :                     }
    4723         208 :                     if (bMatchBBOX &&
    4724          48 :                         (m_asAttributeFilterConstraints.empty() ||
    4725           0 :                          !SkipToNextFeatureDueToAttributeFilter()))
    4726             :                     {
    4727          48 :                         bReturnFeature = true;
    4728          48 :                         break;
    4729             :                     }
    4730             : 
    4731         112 :                     IncrFeatureIdx();
    4732         112 :                     m_nIdxInBatch++;
    4733         112 :                     if (m_nIdxInBatch == m_poBatch->num_rows())
    4734             :                     {
    4735          16 :                         m_bEOF = !ReadNextBatch();
    4736          16 :                         if (m_bEOF)
    4737          16 :                             return nullptr;
    4738           0 :                         break;
    4739             :                     }
    4740          96 :                 }
    4741          48 :             } while (!bReturnFeature);
    4742             :         }
    4743         465 :         else if (iCol >= 0 && m_aeGeomEncoding[m_iGeomFieldFilter] ==
    4744             :                                   OGRArrowGeomEncoding::GEOARROW_STRUCT_POLYGON)
    4745             :         {
    4746             :             bool bReturnFeature;
    4747           0 :             do
    4748             :             {
    4749          96 :                 bReturnFeature = false;
    4750          96 :                 auto array = GetStorageArray(m_poBatchColumns[iCol].get());
    4751          96 :                 CPLAssert(array->type_id() == arrow::Type::LIST);
    4752          96 :                 const auto listOfRingsArray =
    4753             :                     static_cast<const arrow::ListArray *>(array);
    4754          96 :                 CPLAssert(listOfRingsArray->values()->type_id() ==
    4755             :                           arrow::Type::LIST);
    4756             :                 const auto listOfRingsValues =
    4757             :                     std::static_pointer_cast<arrow::ListArray>(
    4758          96 :                         listOfRingsArray->values());
    4759          96 :                 CPLAssert(listOfRingsValues->values()->type_id() ==
    4760             :                           arrow::Type::STRUCT);
    4761             :                 auto pointValues = std::static_pointer_cast<arrow::StructArray>(
    4762          96 :                     listOfRingsValues->values());
    4763          96 :                 const auto &fields = pointValues->fields();
    4764          96 :                 const auto &fieldX = fields[0];
    4765          96 :                 CPLAssert(fieldX->type_id() == arrow::Type::DOUBLE);
    4766             :                 const auto fieldXDouble =
    4767          96 :                     static_cast<arrow::DoubleArray *>(fieldX.get());
    4768          96 :                 const auto &fieldY = fields[1];
    4769          96 :                 CPLAssert(fieldY->type_id() == arrow::Type::DOUBLE);
    4770             :                 const auto fieldYDouble =
    4771          96 :                     static_cast<arrow::DoubleArray *>(fieldY.get());
    4772             : 
    4773             :                 while (true)
    4774             :                 {
    4775         240 :                     bool bMatchBBOX = false;
    4776         240 :                     if (!listOfRingsArray->IsNull(m_nIdxInBatch))
    4777             :                     {
    4778         184 :                         OGREnvelope sEnvelope;
    4779             :                         const auto nRings =
    4780         184 :                             listOfRingsArray->value_length(m_nIdxInBatch);
    4781             :                         const auto nRingOffset =
    4782         184 :                             listOfRingsArray->value_offset(m_nIdxInBatch);
    4783         184 :                         if (nRings >= 1)
    4784             :                         {
    4785             :                             const auto nPoints =
    4786         128 :                                 listOfRingsValues->value_length(nRingOffset);
    4787             :                             const auto nPointOffset =
    4788         128 :                                 listOfRingsValues->value_offset(nRingOffset);
    4789             :                             const double *padfRawXValue =
    4790         128 :                                 fieldXDouble->raw_values() + nPointOffset;
    4791             :                             const double *padfRawYValue =
    4792         128 :                                 fieldYDouble->raw_values() + nPointOffset;
    4793         688 :                             for (auto l = decltype(nPoints){0}; l < nPoints;
    4794             :                                  ++l)
    4795             :                             {
    4796         560 :                                 sEnvelope.Merge(padfRawXValue[l],
    4797         560 :                                                 padfRawYValue[l]);
    4798             :                             }
    4799             :                             // for bounding box, only the first ring matters
    4800             : 
    4801         128 :                             if (m_sFilterEnvelope.Intersects(sEnvelope))
    4802             :                             {
    4803          72 :                                 bMatchBBOX = true;
    4804             :                             }
    4805             :                         }
    4806             :                     }
    4807         312 :                     if (bMatchBBOX &&
    4808          72 :                         (m_asAttributeFilterConstraints.empty() ||
    4809           0 :                          !SkipToNextFeatureDueToAttributeFilter()))
    4810             :                     {
    4811          72 :                         bReturnFeature = true;
    4812          72 :                         break;
    4813             :                     }
    4814             : 
    4815         168 :                     IncrFeatureIdx();
    4816         168 :                     m_nIdxInBatch++;
    4817         168 :                     if (m_nIdxInBatch == m_poBatch->num_rows())
    4818             :                     {
    4819          24 :                         m_bEOF = !ReadNextBatch();
    4820          24 :                         if (m_bEOF)
    4821          24 :                             return nullptr;
    4822           0 :                         break;
    4823             :                     }
    4824         144 :                 }
    4825          72 :             } while (!bReturnFeature);
    4826             :         }
    4827         738 :         else if (iCol >= 0 &&
    4828         369 :                  m_aeGeomEncoding[m_iGeomFieldFilter] ==
    4829             :                      OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOINT)
    4830             :         {
    4831             :             bool bReturnFeature;
    4832           0 :             do
    4833             :             {
    4834          64 :                 bReturnFeature = false;
    4835          64 :                 auto array = GetStorageArray(m_poBatchColumns[iCol].get());
    4836          64 :                 CPLAssert(array->type_id() == arrow::Type::LIST);
    4837          64 :                 const auto listArray =
    4838             :                     static_cast<const arrow::ListArray *>(array);
    4839          64 :                 CPLAssert(listArray->values()->type_id() ==
    4840             :                           arrow::Type::STRUCT);
    4841             :                 auto pointValues = std::static_pointer_cast<arrow::StructArray>(
    4842          64 :                     listArray->values());
    4843          64 :                 const auto &fields = pointValues->fields();
    4844          64 :                 const auto &fieldX = fields[0];
    4845          64 :                 CPLAssert(fieldX->type_id() == arrow::Type::DOUBLE);
    4846             :                 const auto fieldXDouble =
    4847          64 :                     static_cast<arrow::DoubleArray *>(fieldX.get());
    4848          64 :                 const auto &fieldY = fields[1];
    4849          64 :                 CPLAssert(fieldY->type_id() == arrow::Type::DOUBLE);
    4850             :                 const auto fieldYDouble =
    4851          64 :                     static_cast<arrow::DoubleArray *>(fieldY.get());
    4852             : 
    4853             :                 while (true)
    4854             :                 {
    4855         160 :                     bool bMatchBBOX = false;
    4856         160 :                     if (!listArray->IsNull(m_nIdxInBatch))
    4857             :                     {
    4858             :                         const auto nPoints =
    4859         128 :                             listArray->value_length(m_nIdxInBatch);
    4860             :                         const auto nPointOffset =
    4861         128 :                             listArray->value_offset(m_nIdxInBatch);
    4862         128 :                         if (nPoints > 0)
    4863             :                         {
    4864             :                             const double *padfRawXValue =
    4865          96 :                                 fieldXDouble->raw_values() + nPointOffset;
    4866             :                             const double *padfRawYValue =
    4867          96 :                                 fieldYDouble->raw_values() + nPointOffset;
    4868         176 :                             for (auto l = decltype(nPoints){0}; l < nPoints;
    4869             :                                  ++l)
    4870             :                             {
    4871         128 :                                 if (padfRawXValue[l] >=
    4872         128 :                                         m_sFilterEnvelope.MinX &&
    4873         108 :                                     padfRawYValue[l] >=
    4874         108 :                                         m_sFilterEnvelope.MinY &&
    4875          88 :                                     padfRawXValue[l] <=
    4876          88 :                                         m_sFilterEnvelope.MaxX &&
    4877          68 :                                     padfRawYValue[l] <= m_sFilterEnvelope.MaxY)
    4878             :                                 {
    4879          48 :                                     bMatchBBOX = true;
    4880          48 :                                     break;
    4881             :                                 }
    4882             :                             }
    4883             :                         }
    4884             :                     }
    4885         208 :                     if (bMatchBBOX &&
    4886          48 :                         (m_asAttributeFilterConstraints.empty() ||
    4887           0 :                          !SkipToNextFeatureDueToAttributeFilter()))
    4888             :                     {
    4889          48 :                         bReturnFeature = true;
    4890          48 :                         break;
    4891             :                     }
    4892             : 
    4893         112 :                     IncrFeatureIdx();
    4894         112 :                     m_nIdxInBatch++;
    4895         112 :                     if (m_nIdxInBatch == m_poBatch->num_rows())
    4896             :                     {
    4897          16 :                         m_bEOF = !ReadNextBatch();
    4898          16 :                         if (m_bEOF)
    4899          16 :                             return nullptr;
    4900           0 :                         break;
    4901             :                     }
    4902          96 :                 }
    4903          48 :             } while (!bReturnFeature);
    4904             :         }
    4905         610 :         else if (iCol >= 0 &&
    4906         305 :                  m_aeGeomEncoding[m_iGeomFieldFilter] ==
    4907             :                      OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTILINESTRING)
    4908             :         {
    4909             :             bool bReturnFeature;
    4910           0 :             do
    4911             :             {
    4912          88 :                 bReturnFeature = false;
    4913          88 :                 auto array = GetStorageArray(m_poBatchColumns[iCol].get());
    4914          88 :                 CPLAssert(array->type_id() == arrow::Type::LIST);
    4915          88 :                 auto listOfPartsArray =
    4916             :                     static_cast<const arrow::ListArray *>(array);
    4917          88 :                 CPLAssert(listOfPartsArray->values()->type_id() ==
    4918             :                           arrow::Type::LIST);
    4919             :                 auto listOfPartsValues =
    4920             :                     std::static_pointer_cast<arrow::ListArray>(
    4921          88 :                         listOfPartsArray->values());
    4922          88 :                 CPLAssert(listOfPartsValues->values()->type_id() ==
    4923             :                           arrow::Type::STRUCT);
    4924             :                 auto pointValues = std::static_pointer_cast<arrow::StructArray>(
    4925          88 :                     listOfPartsValues->values());
    4926          88 :                 const auto &fields = pointValues->fields();
    4927          88 :                 const auto &fieldX = fields[0];
    4928          88 :                 CPLAssert(fieldX->type_id() == arrow::Type::DOUBLE);
    4929             :                 const auto fieldXDouble =
    4930          88 :                     static_cast<arrow::DoubleArray *>(fieldX.get());
    4931          88 :                 const auto &fieldY = fields[1];
    4932          88 :                 CPLAssert(fieldY->type_id() == arrow::Type::DOUBLE);
    4933             :                 const auto fieldYDouble =
    4934          88 :                     static_cast<arrow::DoubleArray *>(fieldY.get());
    4935             : 
    4936             :                 while (true)
    4937             :                 {
    4938         200 :                     bool bMatchBBOX = false;
    4939         200 :                     if (!listOfPartsArray->IsNull(m_nIdxInBatch))
    4940             :                     {
    4941             :                         const auto nParts =
    4942         160 :                             listOfPartsArray->value_length(m_nIdxInBatch);
    4943             :                         const auto nPartOffset =
    4944         160 :                             listOfPartsArray->value_offset(m_nIdxInBatch);
    4945         312 :                         for (auto j = decltype(nParts){0};
    4946         312 :                              j < nParts && !bMatchBBOX; j++)
    4947             :                         {
    4948         152 :                             OGREnvelope sEnvelope;
    4949             :                             const auto nPoints =
    4950         152 :                                 listOfPartsValues->value_length(nPartOffset +
    4951             :                                                                 j);
    4952             :                             const auto nPointOffset =
    4953         152 :                                 listOfPartsValues->value_offset(nPartOffset +
    4954             :                                                                 j);
    4955             :                             const double *padfRawXValue =
    4956         152 :                                 fieldXDouble->raw_values() + nPointOffset;
    4957             :                             const double *padfRawYValue =
    4958         152 :                                 fieldYDouble->raw_values() + nPointOffset;
    4959         488 :                             for (auto l = decltype(nPoints){0}; l < nPoints;
    4960             :                                  ++l)
    4961             :                             {
    4962         336 :                                 sEnvelope.Merge(padfRawXValue[l],
    4963         336 :                                                 padfRawYValue[l]);
    4964             :                             }
    4965             : 
    4966         152 :                             if (m_sFilterEnvelope.Intersects(sEnvelope))
    4967             :                             {
    4968          72 :                                 bMatchBBOX = true;
    4969             :                             }
    4970             :                         }
    4971             :                     }
    4972         272 :                     if (bMatchBBOX &&
    4973          72 :                         (m_asAttributeFilterConstraints.empty() ||
    4974           0 :                          !SkipToNextFeatureDueToAttributeFilter()))
    4975             :                     {
    4976          72 :                         bReturnFeature = true;
    4977          72 :                         break;
    4978             :                     }
    4979             : 
    4980         128 :                     IncrFeatureIdx();
    4981         128 :                     m_nIdxInBatch++;
    4982         128 :                     if (m_nIdxInBatch == m_poBatch->num_rows())
    4983             :                     {
    4984          16 :                         m_bEOF = !ReadNextBatch();
    4985          16 :                         if (m_bEOF)
    4986          16 :                             return nullptr;
    4987           0 :                         break;
    4988             :                     }
    4989         112 :                 }
    4990          72 :             } while (!bReturnFeature);
    4991             :         }
    4992         434 :         else if (iCol >= 0 &&
    4993         217 :                  m_aeGeomEncoding[m_iGeomFieldFilter] ==
    4994             :                      OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOLYGON)
    4995             :         {
    4996             :             bool bReturnFeature;
    4997           0 :             do
    4998             :             {
    4999          96 :                 bReturnFeature = false;
    5000          96 :                 auto array = GetStorageArray(m_poBatchColumns[iCol].get());
    5001          96 :                 CPLAssert(array->type_id() == arrow::Type::LIST);
    5002          96 :                 auto listOfPartsArray =
    5003             :                     static_cast<const arrow::ListArray *>(array);
    5004          96 :                 CPLAssert(listOfPartsArray->values()->type_id() ==
    5005             :                           arrow::Type::LIST);
    5006             :                 auto listOfPartsValues =
    5007             :                     std::static_pointer_cast<arrow::ListArray>(
    5008          96 :                         listOfPartsArray->values());
    5009          96 :                 CPLAssert(listOfPartsValues->values()->type_id() ==
    5010             :                           arrow::Type::LIST);
    5011             :                 auto listOfRingsValues =
    5012             :                     std::static_pointer_cast<arrow::ListArray>(
    5013          96 :                         listOfPartsValues->values());
    5014          96 :                 CPLAssert(listOfRingsValues->values()->type_id() ==
    5015             :                           arrow::Type::STRUCT);
    5016             :                 auto pointValues = std::static_pointer_cast<arrow::StructArray>(
    5017          96 :                     listOfRingsValues->values());
    5018          96 :                 const auto &fields = pointValues->fields();
    5019          96 :                 const auto &fieldX = fields[0];
    5020          96 :                 CPLAssert(fieldX->type_id() == arrow::Type::DOUBLE);
    5021             :                 const auto fieldXDouble =
    5022          96 :                     static_cast<arrow::DoubleArray *>(fieldX.get());
    5023          96 :                 const auto &fieldY = fields[1];
    5024          96 :                 CPLAssert(fieldY->type_id() == arrow::Type::DOUBLE);
    5025             :                 const auto fieldYDouble =
    5026          96 :                     static_cast<arrow::DoubleArray *>(fieldY.get());
    5027             : 
    5028             :                 while (true)
    5029             :                 {
    5030         240 :                     bool bMatchBBOX = false;
    5031         240 :                     if (!listOfPartsArray->IsNull(m_nIdxInBatch))
    5032             :                     {
    5033             :                         const auto nParts =
    5034         188 :                             listOfPartsArray->value_length(m_nIdxInBatch);
    5035             :                         const auto nPartOffset =
    5036         188 :                             listOfPartsArray->value_offset(m_nIdxInBatch);
    5037         356 :                         for (auto j = decltype(nParts){0};
    5038         356 :                              j < nParts && !bMatchBBOX; j++)
    5039             :                         {
    5040         168 :                             OGREnvelope sEnvelope;
    5041         168 :                             const auto nRings = listOfPartsValues->value_length(
    5042         168 :                                 nPartOffset + j);
    5043             :                             const auto nRingOffset =
    5044         168 :                                 listOfPartsValues->value_offset(nPartOffset +
    5045             :                                                                 j);
    5046         168 :                             if (nRings >= 1)
    5047             :                             {
    5048             :                                 const auto nPoints =
    5049         168 :                                     listOfRingsValues->value_length(
    5050             :                                         nRingOffset);
    5051             :                                 const auto nPointOffset =
    5052         168 :                                     listOfRingsValues->value_offset(
    5053             :                                         nRingOffset);
    5054             :                                 const double *padfRawXValue =
    5055         168 :                                     fieldXDouble->raw_values() + nPointOffset;
    5056             :                                 const double *padfRawYValue =
    5057         168 :                                     fieldYDouble->raw_values() + nPointOffset;
    5058         888 :                                 for (auto l = decltype(nPoints){0}; l < nPoints;
    5059             :                                      ++l)
    5060             :                                 {
    5061         720 :                                     sEnvelope.Merge(padfRawXValue[l],
    5062         720 :                                                     padfRawYValue[l]);
    5063             :                                 }
    5064             : 
    5065         168 :                                 if (m_sFilterEnvelope.Intersects(sEnvelope))
    5066             :                                 {
    5067          72 :                                     bMatchBBOX = true;
    5068             :                                 }
    5069             :                                 // for bounding box, only the first ring matters
    5070             :                             }
    5071             :                         }
    5072             :                     }
    5073         312 :                     if (bMatchBBOX &&
    5074          72 :                         (m_asAttributeFilterConstraints.empty() ||
    5075           0 :                          !SkipToNextFeatureDueToAttributeFilter()))
    5076             :                     {
    5077          72 :                         bReturnFeature = true;
    5078          72 :                         break;
    5079             :                     }
    5080             : 
    5081         168 :                     IncrFeatureIdx();
    5082         168 :                     m_nIdxInBatch++;
    5083         168 :                     if (m_nIdxInBatch == m_poBatch->num_rows())
    5084             :                     {
    5085          24 :                         m_bEOF = !ReadNextBatch();
    5086          24 :                         if (m_bEOF)
    5087          24 :                             return nullptr;
    5088           0 :                         break;
    5089             :                     }
    5090         144 :                 }
    5091          72 :             } while (!bReturnFeature);
    5092             :         }
    5093         121 :         else if (iCol >= 0)
    5094             :         {
    5095         121 :             auto array = GetStorageArray(m_poBatchColumns[iCol].get());
    5096             :             while (true)
    5097             :             {
    5098         220 :                 bool bMatchBBOX = false;
    5099             : 
    5100             :                 auto poGeometry = std::unique_ptr<OGRGeometry>(
    5101         220 :                     ReadGeometry(m_iGeomFieldFilter, array, m_nIdxInBatch));
    5102         220 :                 if (poGeometry && !poGeometry->IsEmpty())
    5103             :                 {
    5104         115 :                     OGREnvelope sEnvelope;
    5105         115 :                     poGeometry->getEnvelope(&sEnvelope);
    5106         115 :                     if (m_sFilterEnvelope.Intersects(sEnvelope))
    5107             :                     {
    5108          88 :                         bMatchBBOX = true;
    5109             :                     }
    5110             :                 }
    5111         220 :                 if (bMatchBBOX && (m_asAttributeFilterConstraints.empty() ||
    5112           0 :                                    !SkipToNextFeatureDueToAttributeFilter()))
    5113             :                 {
    5114          88 :                     break;
    5115             :                 }
    5116             : 
    5117         132 :                 IncrFeatureIdx();
    5118         132 :                 m_nIdxInBatch++;
    5119         132 :                 if (m_nIdxInBatch == m_poBatch->num_rows())
    5120             :                 {
    5121          33 :                     m_bEOF = !ReadNextBatch();
    5122          33 :                     if (m_bEOF)
    5123          33 :                         return nullptr;
    5124           0 :                     array = GetStorageArray(m_poBatchColumns[iCol].get());
    5125             :                 }
    5126          99 :             }
    5127        1005 :         }
    5128             :     }
    5129             : 
    5130        9894 :     else if (!m_asAttributeFilterConstraints.empty())
    5131             :     {
    5132             :         while (true)
    5133             :         {
    5134         445 :             if (!SkipToNextFeatureDueToAttributeFilter())
    5135             :             {
    5136         243 :                 break;
    5137             :             }
    5138             : 
    5139         202 :             IncrFeatureIdx();
    5140         202 :             m_nIdxInBatch++;
    5141         202 :             if (m_nIdxInBatch == m_poBatch->num_rows())
    5142             :             {
    5143          90 :                 m_bEOF = !ReadNextBatch();
    5144          90 :                 if (m_bEOF)
    5145          52 :                     return nullptr;
    5146             :             }
    5147             :         }
    5148             :     }
    5149             : 
    5150       10847 :     auto poFeature = ReadFeature(m_nIdxInBatch, m_poBatchColumns);
    5151             : 
    5152       10847 :     if (m_iFIDArrowColumn < 0)
    5153        7449 :         poFeature->SetFID(m_nFeatureIdx);
    5154             : 
    5155       10847 :     IncrFeatureIdx();
    5156       10847 :     m_nIdxInBatch++;
    5157             : 
    5158       10847 :     return poFeature;
    5159             : }
    5160             : 
    5161             : /************************************************************************/
    5162             : /*                       GetExtentFromMetadata()                        */
    5163             : /************************************************************************/
    5164             : 
    5165             : inline OGRErr
    5166         956 : OGRArrowLayer::GetExtentFromMetadata(const CPLJSONObject &oJSONDef,
    5167             :                                      OGREnvelope3D *psExtent)
    5168             : {
    5169        2868 :     const auto oBBox = oJSONDef.GetArray("bbox");
    5170         956 :     if (oBBox.IsValid() && oBBox.Size() == 4)
    5171             :     {
    5172         569 :         psExtent->MinX = oBBox[0].ToDouble();
    5173         569 :         psExtent->MinY = oBBox[1].ToDouble();
    5174         569 :         psExtent->MinZ = std::numeric_limits<double>::infinity();
    5175         569 :         psExtent->MaxX = oBBox[2].ToDouble();
    5176         569 :         psExtent->MaxY = oBBox[3].ToDouble();
    5177         569 :         psExtent->MaxZ = -std::numeric_limits<double>::infinity();
    5178         569 :         if (psExtent->MinX <= psExtent->MaxX)
    5179         569 :             return OGRERR_NONE;
    5180             :     }
    5181         387 :     else if (oBBox.IsValid() && oBBox.Size() == 6)
    5182             :     {
    5183         318 :         psExtent->MinX = oBBox[0].ToDouble();
    5184         318 :         psExtent->MinY = oBBox[1].ToDouble();
    5185         318 :         psExtent->MinZ = oBBox[2].ToDouble();
    5186         318 :         psExtent->MaxX = oBBox[3].ToDouble();
    5187         318 :         psExtent->MaxY = oBBox[4].ToDouble();
    5188         318 :         psExtent->MaxZ = oBBox[5].ToDouble();
    5189         318 :         if (psExtent->MinX <= psExtent->MaxX)
    5190         318 :             return OGRERR_NONE;
    5191             :     }
    5192          69 :     return OGRERR_FAILURE;
    5193             : }
    5194             : 
    5195             : /************************************************************************/
    5196             : /*                         ISetSpatialFilter()                          */
    5197             : /************************************************************************/
    5198             : 
    5199        1713 : inline OGRErr OGRArrowLayer::ISetSpatialFilter(int iGeomField,
    5200             :                                                const OGRGeometry *poGeomIn)
    5201             : 
    5202             : {
    5203             :     // When changing filters, we need to invalidate cached batches, as
    5204             :     // PostFilterArrowArray() has potentially modified array contents
    5205        1713 :     if (m_poFilterGeom)
    5206        1044 :         InvalidateCachedBatches();
    5207             : 
    5208        1713 :     m_bSpatialFilterIntersectsLayerExtent = true;
    5209        1713 :     if (iGeomField < GetLayerDefn()->GetGeomFieldCount())
    5210             :     {
    5211        1697 :         m_iGeomFieldFilter = iGeomField;
    5212        1697 :         if (InstallFilter(poGeomIn))
    5213        1487 :             ResetReading();
    5214        1697 :         if (m_poFilterGeom != nullptr)
    5215             :         {
    5216        1420 :             OGREnvelope sLayerExtent;
    5217        1420 :             if (FastGetExtent(iGeomField, &sLayerExtent))
    5218             :             {
    5219         812 :                 m_bSpatialFilterIntersectsLayerExtent =
    5220         812 :                     CPL_TO_BOOL(m_sFilterEnvelope.Intersects(sLayerExtent));
    5221             :             }
    5222             :         }
    5223             :     }
    5224             : 
    5225        1713 :     SetBatch(m_poBatch);
    5226        1713 :     return OGRERR_NONE;
    5227             : }
    5228             : 
    5229             : /************************************************************************/
    5230             : /*                           FastGetExtent()                            */
    5231             : /************************************************************************/
    5232             : 
    5233         923 : inline bool OGRArrowLayer::FastGetExtent(int iGeomField,
    5234             :                                          OGREnvelope *psExtent) const
    5235             : {
    5236             :     {
    5237         923 :         const auto oIter = m_oMapExtents.find(iGeomField);
    5238         923 :         if (oIter != m_oMapExtents.end())
    5239             :         {
    5240           5 :             *psExtent = oIter->second;
    5241           5 :             return true;
    5242             :         }
    5243             :     }
    5244             : 
    5245             :     const char *pszGeomFieldName =
    5246         918 :         m_poFeatureDefn->GetGeomFieldDefn(iGeomField)->GetNameRef();
    5247         918 :     const auto oIter = m_oMapGeometryColumns.find(pszGeomFieldName);
    5248        1834 :     if (oIter != m_oMapGeometryColumns.end() &&
    5249         916 :         CPLTestBool(CPLGetConfigOption(
    5250        1834 :             ("OGR_" + GetDriverUCName() + "_USE_BBOX").c_str(), "YES")))
    5251             :     {
    5252         910 :         const auto &oJSONDef = oIter->second;
    5253         910 :         OGREnvelope3D sEnvelope3D;
    5254         910 :         if (GetExtentFromMetadata(oJSONDef, &sEnvelope3D) == OGRERR_NONE)
    5255             :         {
    5256         851 :             *psExtent = sEnvelope3D;
    5257         851 :             return true;
    5258             :         }
    5259             :     }
    5260          67 :     return false;
    5261             : }
    5262             : 
    5263             : /************************************************************************/
    5264             : /*                             IGetExtent()                             */
    5265             : /************************************************************************/
    5266             : 
    5267          67 : inline OGRErr OGRArrowLayer::IGetExtent(int iGeomField, OGREnvelope *psExtent,
    5268             :                                         bool bForce)
    5269             : {
    5270          67 :     if (FastGetExtent(iGeomField, psExtent))
    5271             :     {
    5272          46 :         return OGRERR_NONE;
    5273             :     }
    5274             : 
    5275          21 :     if (!bForce && !CanRunNonForcedGetExtent())
    5276             :     {
    5277           0 :         return OGRERR_FAILURE;
    5278             :     }
    5279             : 
    5280             :     int iCol;
    5281          21 :     if (m_bIgnoredFields)
    5282             :     {
    5283           0 :         iCol = m_anMapGeomFieldIndexToArrayIndex[iGeomField];
    5284             :     }
    5285             :     else
    5286             :     {
    5287          21 :         iCol = m_anMapGeomFieldIndexToArrowColumn[iGeomField];
    5288             :     }
    5289          21 :     if (iCol < 0)
    5290             :     {
    5291           0 :         return OGRERR_FAILURE;
    5292             :     }
    5293             : 
    5294          21 :     if (m_aeGeomEncoding[iGeomField] == OGRArrowGeomEncoding::WKB)
    5295             :     {
    5296           7 :         ResetReading();
    5297           7 :         if (m_poBatch == nullptr)
    5298             :         {
    5299           6 :             m_bEOF = !ReadNextBatch();
    5300           6 :             if (m_bEOF)
    5301           0 :                 return OGRERR_FAILURE;
    5302             :         }
    5303           7 :         *psExtent = OGREnvelope();
    5304             : 
    5305           7 :         auto array = GetStorageArray(m_poBatchColumns[iCol].get());
    5306           7 :         const arrow::BinaryArray *smallArray = nullptr;
    5307           7 :         const arrow::LargeBinaryArray *largeArray = nullptr;
    5308           7 :         if (array->type_id() == arrow::Type::BINARY)
    5309           7 :             smallArray = static_cast<const arrow::BinaryArray *>(array);
    5310             :         else
    5311             :         {
    5312           0 :             CPLAssert(array->type_id() == arrow::Type::LARGE_BINARY);
    5313           0 :             largeArray = static_cast<const arrow::LargeBinaryArray *>(array);
    5314             :         }
    5315           7 :         OGREnvelope sEnvelope;
    5316             :         while (true)
    5317             :         {
    5318          32 :             if (!array->IsNull(m_nIdxInBatch))
    5319             :             {
    5320          30 :                 if (smallArray)
    5321             :                 {
    5322          30 :                     int out_length = 0;
    5323             :                     const uint8_t *data =
    5324          30 :                         smallArray->GetValue(m_nIdxInBatch, &out_length);
    5325          30 :                     if (OGRWKBGetBoundingBox(data, out_length, sEnvelope))
    5326             :                     {
    5327          30 :                         psExtent->Merge(sEnvelope);
    5328             :                     }
    5329             :                 }
    5330             :                 else
    5331             :                 {
    5332           0 :                     assert(largeArray);
    5333           0 :                     int64_t out_length = 0;
    5334             :                     const uint8_t *data =
    5335           0 :                         largeArray->GetValue(m_nIdxInBatch, &out_length);
    5336           0 :                     if (out_length < INT_MAX &&
    5337           0 :                         OGRWKBGetBoundingBox(data, static_cast<int>(out_length),
    5338             :                                              sEnvelope))
    5339             :                     {
    5340           0 :                         psExtent->Merge(sEnvelope);
    5341             :                     }
    5342             :                 }
    5343             :             }
    5344             : 
    5345          32 :             m_nIdxInBatch++;
    5346          32 :             if (m_nIdxInBatch == m_poBatch->num_rows())
    5347             :             {
    5348           7 :                 m_bEOF = !ReadNextBatch();
    5349           7 :                 if (m_bEOF)
    5350             :                 {
    5351           7 :                     ResetReading();
    5352           7 :                     if (psExtent->IsInit())
    5353             :                     {
    5354           7 :                         m_oMapExtents[iGeomField] = *psExtent;
    5355           7 :                         return OGRERR_NONE;
    5356             :                     }
    5357           0 :                     return OGRERR_FAILURE;
    5358             :                 }
    5359           0 :                 array = GetStorageArray(m_poBatchColumns[iCol].get());
    5360           0 :                 if (array->type_id() == arrow::Type::BINARY)
    5361           0 :                     smallArray = static_cast<const arrow::BinaryArray *>(array);
    5362             :                 else
    5363             :                 {
    5364           0 :                     CPLAssert(array->type_id() == arrow::Type::LARGE_BINARY);
    5365           0 :                     largeArray =
    5366             :                         static_cast<const arrow::LargeBinaryArray *>(array);
    5367             :                 }
    5368             :             }
    5369          25 :         }
    5370             :     }
    5371          14 :     else if (m_aeGeomEncoding[iGeomField] ==
    5372             :              OGRArrowGeomEncoding::GEOARROW_FSL_MULTIPOLYGON)
    5373             :     {
    5374           0 :         ResetReading();
    5375           0 :         if (m_poBatch == nullptr)
    5376             :         {
    5377           0 :             m_bEOF = !ReadNextBatch();
    5378           0 :             if (m_bEOF)
    5379           0 :                 return OGRERR_FAILURE;
    5380             :         }
    5381           0 :         *psExtent = OGREnvelope();
    5382             : 
    5383             :         const auto poGeomFieldDefn =
    5384           0 :             m_poFeatureDefn->GetGeomFieldDefn(iGeomField);
    5385           0 :         const auto eGeomType = poGeomFieldDefn->GetType();
    5386           0 :         const bool bHasZ = CPL_TO_BOOL(OGR_GT_HasZ(eGeomType));
    5387           0 :         const bool bHasM = CPL_TO_BOOL(OGR_GT_HasM(eGeomType));
    5388           0 :         const int nDim = 2 + (bHasZ ? 1 : 0) + (bHasM ? 1 : 0);
    5389             : 
    5390           0 :     begin_multipolygon:
    5391           0 :         auto array = m_poBatchColumns[iCol].get();
    5392           0 :         CPLAssert(array->type_id() == arrow::Type::LIST);
    5393           0 :         auto listOfPartsArray = static_cast<const arrow::ListArray *>(array);
    5394           0 :         CPLAssert(listOfPartsArray->values()->type_id() == arrow::Type::LIST);
    5395             :         auto listOfPartsValues = std::static_pointer_cast<arrow::ListArray>(
    5396           0 :             listOfPartsArray->values());
    5397           0 :         CPLAssert(listOfPartsValues->values()->type_id() == arrow::Type::LIST);
    5398             :         auto listOfRingsValues = std::static_pointer_cast<arrow::ListArray>(
    5399           0 :             listOfPartsValues->values());
    5400           0 :         CPLAssert(listOfRingsValues->values()->type_id() ==
    5401             :                   arrow::Type::FIXED_SIZE_LIST);
    5402             :         auto listOfPointsValues =
    5403             :             std::static_pointer_cast<arrow::FixedSizeListArray>(
    5404           0 :                 listOfRingsValues->values());
    5405           0 :         CPLAssert(listOfPointsValues->values()->type_id() ==
    5406             :                   arrow::Type::DOUBLE);
    5407             :         auto pointValues = std::static_pointer_cast<arrow::DoubleArray>(
    5408           0 :             listOfPointsValues->values());
    5409             : 
    5410             :         while (true)
    5411             :         {
    5412           0 :             if (!listOfPartsArray->IsNull(m_nIdxInBatch))
    5413             :             {
    5414             :                 const auto nParts =
    5415           0 :                     listOfPartsArray->value_length(m_nIdxInBatch);
    5416             :                 const auto nPartOffset =
    5417           0 :                     listOfPartsArray->value_offset(m_nIdxInBatch);
    5418           0 :                 for (auto j = decltype(nParts){0}; j < nParts; j++)
    5419             :                 {
    5420             :                     const auto nRings =
    5421           0 :                         listOfPartsValues->value_length(nPartOffset + j);
    5422             :                     const auto nRingOffset =
    5423           0 :                         listOfPartsValues->value_offset(nPartOffset + j);
    5424           0 :                     if (nRings >= 1)
    5425             :                     {
    5426             :                         const auto nPoints =
    5427           0 :                             listOfRingsValues->value_length(nRingOffset);
    5428             :                         const auto nPointOffset =
    5429           0 :                             listOfRingsValues->value_offset(nRingOffset) * nDim;
    5430             :                         const double *padfRawValue =
    5431           0 :                             pointValues->raw_values() + nPointOffset;
    5432           0 :                         for (auto l = decltype(nPoints){0}; l < nPoints; ++l)
    5433             :                         {
    5434           0 :                             psExtent->Merge(padfRawValue[nDim * l],
    5435           0 :                                             padfRawValue[nDim * l + 1]);
    5436             :                         }
    5437             :                         // for bounding box, only the first ring matters
    5438             :                     }
    5439             :                 }
    5440             :             }
    5441             : 
    5442           0 :             m_nIdxInBatch++;
    5443           0 :             if (m_nIdxInBatch == m_poBatch->num_rows())
    5444             :             {
    5445           0 :                 m_bEOF = !ReadNextBatch();
    5446           0 :                 if (m_bEOF)
    5447             :                 {
    5448           0 :                     ResetReading();
    5449           0 :                     if (psExtent->IsInit())
    5450             :                     {
    5451           0 :                         m_oMapExtents[iGeomField] = *psExtent;
    5452           0 :                         return OGRERR_NONE;
    5453             :                     }
    5454           0 :                     return OGRERR_FAILURE;
    5455             :                 }
    5456           0 :                 goto begin_multipolygon;
    5457             :             }
    5458           0 :         }
    5459             :     }
    5460             : 
    5461          14 :     return OGRLayer::IGetExtent(iGeomField, psExtent, bForce);
    5462             : }
    5463             : 
    5464             : /************************************************************************/
    5465             : /*                          FastGetExtent3D()                           */
    5466             : /************************************************************************/
    5467             : 
    5468          38 : inline bool OGRArrowLayer::FastGetExtent3D(int iGeomField,
    5469             :                                            OGREnvelope3D *psExtent) const
    5470             : {
    5471             :     {
    5472          38 :         const auto oIter = m_oMapExtents3D.find(iGeomField);
    5473          38 :         if (oIter != m_oMapExtents3D.end())
    5474             :         {
    5475           0 :             *psExtent = oIter->second;
    5476           0 :             return true;
    5477             :         }
    5478             :     }
    5479             : 
    5480             :     const char *pszGeomFieldName =
    5481          38 :         m_poFeatureDefn->GetGeomFieldDefn(iGeomField)->GetNameRef();
    5482          38 :     const auto oIter = m_oMapGeometryColumns.find(pszGeomFieldName);
    5483          76 :     if (oIter != m_oMapGeometryColumns.end() &&
    5484          38 :         CPLTestBool(CPLGetConfigOption(
    5485          76 :             ("OGR_" + GetDriverUCName() + "_USE_BBOX").c_str(), "YES")))
    5486             :     {
    5487          36 :         const auto &oJSONDef = oIter->second;
    5488          64 :         if (GetExtentFromMetadata(oJSONDef, psExtent) == OGRERR_NONE &&
    5489          28 :             psExtent->Is3D())
    5490             :         {
    5491           2 :             return true;
    5492             :         }
    5493             :     }
    5494          36 :     return false;
    5495             : }
    5496             : 
    5497             : /************************************************************************/
    5498             : /*                            IGetExtent3D()                            */
    5499             : /************************************************************************/
    5500             : 
    5501          19 : inline OGRErr OGRArrowLayer::IGetExtent3D(int iGeomField,
    5502             :                                           OGREnvelope3D *psExtent, bool bForce)
    5503             : {
    5504          19 :     if (FastGetExtent3D(iGeomField, psExtent))
    5505             :     {
    5506           1 :         return OGRERR_NONE;
    5507             :     }
    5508             : 
    5509          18 :     return OGRLayer::IGetExtent3D(iGeomField, psExtent, bForce);
    5510             : }
    5511             : 
    5512             : /************************************************************************/
    5513             : /*                     OverrideArrowSchemaRelease()                     */
    5514             : /************************************************************************/
    5515             : 
    5516             : template <class T>
    5517         611 : static void OverrideArrowRelease(OGRArrowDataset *poDS, T *obj)
    5518             : {
    5519             :     // We override the release callback, since it can use the memory pool,
    5520             :     // and we need to make sure it is still alive when the object (ArrowArray
    5521             :     // or ArrowSchema) is deleted
    5522         611 :     struct OverriddenPrivate
    5523             :     {
    5524             :         OverriddenPrivate() = default;
    5525             :         OverriddenPrivate(const OverriddenPrivate &) = delete;
    5526             :         OverriddenPrivate &operator=(const OverriddenPrivate &) = delete;
    5527             : 
    5528             :         std::shared_ptr<arrow::MemoryPool> poMemoryPool{};
    5529             :         void (*pfnPreviousRelease)(T *) = nullptr;
    5530             :         void *pPreviousPrivateData = nullptr;
    5531             : 
    5532         611 :         static void release(T *l_obj)
    5533             :         {
    5534         611 :             OverriddenPrivate *myPrivate =
    5535             :                 static_cast<OverriddenPrivate *>(l_obj->private_data);
    5536         611 :             l_obj->private_data = myPrivate->pPreviousPrivateData;
    5537         611 :             l_obj->release = myPrivate->pfnPreviousRelease;
    5538         611 :             l_obj->release(l_obj);
    5539         611 :             delete myPrivate;
    5540         611 :         }
    5541             :     };
    5542             : 
    5543         611 :     auto overriddenPrivate = new OverriddenPrivate();
    5544         611 :     overriddenPrivate->poMemoryPool = poDS->GetSharedMemoryPool();
    5545         611 :     overriddenPrivate->pPreviousPrivateData = obj->private_data;
    5546         611 :     overriddenPrivate->pfnPreviousRelease = obj->release;
    5547             : 
    5548         611 :     obj->release = OverriddenPrivate::release;
    5549         611 :     obj->private_data = overriddenPrivate;
    5550         611 : }
    5551             : 
    5552             : /************************************************************************/
    5553             : /*                  UseRecordBatchBaseImplementation()                  */
    5554             : /************************************************************************/
    5555             : 
    5556         475 : inline bool OGRArrowLayer::UseRecordBatchBaseImplementation() const
    5557             : {
    5558         475 :     if (CPLTestBool(CPLGetConfigOption("OGR_ARROW_STREAM_BASE_IMPL", "NO")))
    5559             :     {
    5560           4 :         return true;
    5561             :     }
    5562             : 
    5563         471 :     if (m_aosArrowArrayStreamOptions.FetchBool(GAS_OPT_DATETIME_AS_STRING,
    5564             :                                                false))
    5565             :     {
    5566           1 :         const int nFieldCount = m_poFeatureDefn->GetFieldCount();
    5567          14 :         for (int i = 0; i < nFieldCount; ++i)
    5568             :         {
    5569          14 :             const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(i);
    5570          28 :             if (!poFieldDefn->IsIgnored() &&
    5571          14 :                 poFieldDefn->GetType() == OFTDateTime)
    5572             :             {
    5573           1 :                 CPLDebug("ARROW",
    5574             :                          "DATETIME_AS_STRING=YES not compatible of fast "
    5575             :                          "Arrow implementation");
    5576           1 :                 return true;
    5577             :             }
    5578             :         }
    5579             :     }
    5580             : 
    5581         470 :     if (EQUAL(m_aosArrowArrayStreamOptions.FetchNameValueDef(
    5582             :                   "GEOMETRY_ENCODING", ""),
    5583             :               "WKB"))
    5584             :     {
    5585         131 :         const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount();
    5586         181 :         for (int i = 0; i < nGeomFieldCount; i++)
    5587             :         {
    5588         146 :             if (!m_poFeatureDefn->GetGeomFieldDefn(i)->IsIgnored() &&
    5589         259 :                 m_aeGeomEncoding[i] != OGRArrowGeomEncoding::WKB &&
    5590         113 :                 m_aeGeomEncoding[i] != OGRArrowGeomEncoding::WKT)
    5591             :             {
    5592          96 :                 CPLDebug("ARROW", "Geometry encoding not compatible of fast "
    5593             :                                   "Arrow implementation");
    5594          96 :                 return true;
    5595             :             }
    5596             :         }
    5597             :     }
    5598             : 
    5599         374 :     if (m_bIgnoredFields)
    5600             :     {
    5601             :         std::vector<int> ignoredState(m_anMapFieldIndexToArrowColumn.size(),
    5602         107 :                                       -1);
    5603        7661 :         for (size_t i = 0; i < m_anMapFieldIndexToArrowColumn.size(); i++)
    5604             :         {
    5605        7555 :             const int nArrowCol = m_anMapFieldIndexToArrowColumn[i][0];
    5606        7555 :             if (nArrowCol >= static_cast<int>(ignoredState.size()))
    5607           2 :                 ignoredState.resize(nArrowCol + 1, -1);
    5608             :             const auto bIsIgnored =
    5609        7555 :                 m_poFeatureDefn->GetFieldDefn(static_cast<int>(i))->IsIgnored();
    5610        7555 :             if (ignoredState[nArrowCol] < 0)
    5611             :             {
    5612        7097 :                 ignoredState[nArrowCol] = static_cast<int>(bIsIgnored);
    5613             :             }
    5614             :             else
    5615             :             {
    5616             :                 // struct fields will point to the same arrow column
    5617         458 :                 if (ignoredState[nArrowCol] != static_cast<int>(bIsIgnored))
    5618             :                 {
    5619           1 :                     CPLDebug("ARROW",
    5620             :                              "Inconsistent ignore state for Arrow Columns");
    5621           1 :                     return true;
    5622             :                 }
    5623             :             }
    5624             :         }
    5625             :     }
    5626             : 
    5627         373 :     if (m_poAttrQuery || m_poFilterGeom)
    5628             :     {
    5629         163 :         struct ArrowSchema *psSchema = &m_sCachedSchema;
    5630         163 :         if (psSchema->release)
    5631         104 :             psSchema->release(psSchema);
    5632         163 :         memset(psSchema, 0, sizeof(*psSchema));
    5633             : 
    5634         326 :         const bool bCanPostFilter = GetArrowSchemaInternal(psSchema) == 0 &&
    5635         163 :                                     CanPostFilterArrowArray(psSchema);
    5636         163 :         if (!bCanPostFilter)
    5637          11 :             return true;
    5638             :     }
    5639             : 
    5640         362 :     return false;
    5641             : }
    5642             : 
    5643             : /************************************************************************/
    5644             : /*                           GetArrowStream()                           */
    5645             : /************************************************************************/
    5646             : 
    5647         294 : inline bool OGRArrowLayer::GetArrowStream(struct ArrowArrayStream *out_stream,
    5648             :                                           CSLConstList papszOptions)
    5649             : {
    5650         294 :     if (!OGRLayer::GetArrowStream(out_stream, papszOptions))
    5651           0 :         return false;
    5652             : 
    5653         294 :     m_bUseRecordBatchBaseImplementation = UseRecordBatchBaseImplementation();
    5654         294 :     return true;
    5655             : }
    5656             : 
    5657             : /************************************************************************/
    5658             : /*                           GetArrowSchema()                           */
    5659             : /************************************************************************/
    5660             : 
    5661         434 : inline int OGRArrowLayer::GetArrowSchema(struct ArrowArrayStream *stream,
    5662             :                                          struct ArrowSchema *out_schema)
    5663             : {
    5664         434 :     if (m_bUseRecordBatchBaseImplementation)
    5665         209 :         return OGRLayer::GetArrowSchema(stream, out_schema);
    5666             : 
    5667         225 :     return GetArrowSchemaInternal(out_schema);
    5668             : }
    5669             : 
    5670             : /************************************************************************/
    5671             : /*                       GetArrowSchemaInternal()                       */
    5672             : /************************************************************************/
    5673             : 
    5674       10802 : static bool IsSilentlyIgnoredFormatForGetArrowSchemaArray(const char *format)
    5675             : {
    5676             :     // n: null
    5677       10802 :     return strcmp(format, "n") == 0;
    5678             : }
    5679             : 
    5680             : inline int
    5681         388 : OGRArrowLayer::GetArrowSchemaInternal(struct ArrowSchema *out_schema) const
    5682             : {
    5683         776 :     auto status = arrow::ExportSchema(*m_poSchema, out_schema);
    5684         388 :     if (!status.ok())
    5685             :     {
    5686           0 :         CPLError(CE_Failure, CPLE_AppDefined, "ExportSchema() failed with %s",
    5687           0 :                  status.message().c_str());
    5688           0 :         return EIO;
    5689             :     }
    5690             : 
    5691         388 :     CPLAssert(out_schema->n_children == m_poSchema->num_fields());
    5692             : 
    5693             :     // Remove ignored fields from the ArrowSchema.
    5694             : 
    5695             :     struct FieldDesc
    5696             :     {
    5697             :         bool bIsRegularField =
    5698             :             false;  // true = attribute field, false = geometry field
    5699             :         int nIdx = -1;
    5700             :     };
    5701             : 
    5702             :     // cppcheck-suppress unreadVariable
    5703             :     std::vector<FieldDesc> fieldDesc(
    5704         776 :         static_cast<size_t>(out_schema->n_children));
    5705       21819 :     for (size_t i = 0; i < m_anMapFieldIndexToArrowColumn.size(); i++)
    5706             :     {
    5707       21431 :         const int nArrowCol = m_anMapFieldIndexToArrowColumn[i][0];
    5708       21431 :         if (fieldDesc[nArrowCol].nIdx < 0)
    5709             :         {
    5710       20150 :             fieldDesc[nArrowCol].bIsRegularField = true;
    5711       20150 :             fieldDesc[nArrowCol].nIdx = static_cast<int>(i);
    5712             :         }
    5713             :     }
    5714         790 :     for (size_t i = 0; i < m_anMapGeomFieldIndexToArrowColumn.size(); i++)
    5715             :     {
    5716         402 :         const int nArrowCol = m_anMapGeomFieldIndexToArrowColumn[i];
    5717         402 :         CPLAssert(fieldDesc[nArrowCol].nIdx < 0);
    5718         402 :         fieldDesc[nArrowCol].bIsRegularField = false;
    5719         402 :         fieldDesc[nArrowCol].nIdx = static_cast<int>(i);
    5720             :     }
    5721             : 
    5722         388 :     int j = 0;
    5723             :     const char *pszReqGeomEncoding =
    5724         388 :         m_aosArrowArrayStreamOptions.FetchNameValueDef("GEOMETRY_ENCODING", "");
    5725             : 
    5726         388 :     const char *pszExtensionName = EXTENSION_NAME_OGC_WKB;
    5727         388 :     if (EQUAL(pszReqGeomEncoding, "WKB") || EQUAL(pszReqGeomEncoding, ""))
    5728             :     {
    5729             :         const char *const pszGeometryMetadataEncoding =
    5730         388 :             m_aosArrowArrayStreamOptions.FetchNameValue(
    5731             :                 "GEOMETRY_METADATA_ENCODING");
    5732         388 :         if (pszGeometryMetadataEncoding)
    5733             :         {
    5734           0 :             if (EQUAL(pszGeometryMetadataEncoding, "OGC"))
    5735           0 :                 pszExtensionName = EXTENSION_NAME_OGC_WKB;
    5736           0 :             else if (EQUAL(pszGeometryMetadataEncoding, "GEOARROW"))
    5737           0 :                 pszExtensionName = EXTENSION_NAME_GEOARROW_WKB;
    5738             :             else
    5739           0 :                 CPLError(CE_Warning, CPLE_NotSupported,
    5740             :                          "Unsupported GEOMETRY_METADATA_ENCODING value: %s",
    5741             :                          pszGeometryMetadataEncoding);
    5742             :         }
    5743             :     }
    5744             : 
    5745       21298 :     for (int i = 0; i < out_schema->n_children; ++i)
    5746             :     {
    5747       20910 :         if (fieldDesc[i].nIdx < 0)
    5748             :         {
    5749         358 :             if (m_iFIDArrowColumn == i)
    5750             :             {
    5751          21 :                 out_schema->children[j] = out_schema->children[i];
    5752          21 :                 ++j;
    5753             :             }
    5754         337 :             else if (cpl::contains(m_oSetBBoxArrowColumns, i))
    5755             :             {
    5756             :                 // Remove bounding box columns from exported schema
    5757          82 :                 out_schema->children[i]->release(out_schema->children[i]);
    5758          82 :                 out_schema->children[i] = nullptr;
    5759             :             }
    5760         255 :             else if (IsSilentlyIgnoredFormatForGetArrowSchemaArray(
    5761         255 :                          out_schema->children[i]->format))
    5762             :             {
    5763             :                 // Silently ignore columns with null data type...
    5764         255 :                 out_schema->children[i]->release(out_schema->children[i]);
    5765             :             }
    5766             :             else
    5767             :             {
    5768             :                 // can happen with data types we don't support
    5769           0 :                 if (m_aosArrowArrayStreamOptions.FetchBool(
    5770             :                         "SILENCE_GET_SCHEMA_ERROR", false))
    5771             :                 {
    5772           0 :                     CPLDebug(GetDriverUCName().c_str(),
    5773             :                              "GetArrowSchema() error: fieldDesc[%d].nIdx < 0 "
    5774             :                              "not expected: name=%s, format=%s",
    5775           0 :                              i, out_schema->children[i]->name,
    5776           0 :                              out_schema->children[i]->format);
    5777             :                 }
    5778             :                 else
    5779             :                 {
    5780           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
    5781             :                              "GetArrowSchema() error: fieldDesc[%d].nIdx < 0 "
    5782             :                              "not expected: name=%s, format=%s",
    5783           0 :                              i, out_schema->children[i]->name,
    5784           0 :                              out_schema->children[i]->format);
    5785             :                 }
    5786           0 :                 for (; i < out_schema->n_children; ++i, ++j)
    5787           0 :                     out_schema->children[j] = out_schema->children[i];
    5788           0 :                 out_schema->n_children = j;
    5789             : 
    5790           0 :                 out_schema->release(out_schema);
    5791             : 
    5792           0 :                 return EIO;
    5793             :             }
    5794         358 :             continue;
    5795             :         }
    5796             : 
    5797             :         const auto bIsIgnored =
    5798       20552 :             fieldDesc[i].bIsRegularField
    5799       20552 :                 ? m_poFeatureDefn->GetFieldDefn(fieldDesc[i].nIdx)->IsIgnored()
    5800         402 :                 : m_poFeatureDefn->GetGeomFieldDefn(fieldDesc[i].nIdx)
    5801         402 :                       ->IsIgnored();
    5802       20552 :         if (bIsIgnored)
    5803             :         {
    5804        2912 :             out_schema->children[i]->release(out_schema->children[i]);
    5805             :         }
    5806             :         else
    5807             :         {
    5808       18023 :             if (!fieldDesc[i].bIsRegularField &&
    5809         383 :                 EQUAL(pszReqGeomEncoding, "WKB"))
    5810             :             {
    5811          64 :                 const int iGeomField = fieldDesc[i].nIdx;
    5812          64 :                 if (m_aeGeomEncoding[iGeomField] == OGRArrowGeomEncoding::WKT)
    5813             :                 {
    5814             :                     const auto poGeomFieldDefn =
    5815          17 :                         m_poFeatureDefn->GetGeomFieldDefn(iGeomField);
    5816          17 :                     CPLAssert(strcmp(out_schema->children[i]->name,
    5817             :                                      poGeomFieldDefn->GetNameRef()) == 0);
    5818          17 :                     auto poSchema = CreateSchemaForWKBGeometryColumn(
    5819             :                         poGeomFieldDefn, "z", pszExtensionName);
    5820          17 :                     out_schema->children[i]->release(out_schema->children[i]);
    5821          17 :                     *(out_schema->children[j]) = *poSchema;
    5822          17 :                     CPLFree(poSchema);
    5823             :                 }
    5824          47 :                 else if (m_aeGeomEncoding[iGeomField] !=
    5825             :                          OGRArrowGeomEncoding::WKB)
    5826             :                 {
    5827             :                     // Shouldn't happen if UseRecordBatchBaseImplementation()
    5828             :                     // is up to date
    5829           0 :                     CPLAssert(false);
    5830             :                 }
    5831             :                 else
    5832             :                 {
    5833          47 :                     out_schema->children[j] = out_schema->children[i];
    5834             :                 }
    5835             :             }
    5836             :             else
    5837             :             {
    5838       17576 :                 out_schema->children[j] = out_schema->children[i];
    5839             :             }
    5840             : 
    5841       18023 :             if (!fieldDesc[i].bIsRegularField &&
    5842         383 :                 (EQUAL(pszReqGeomEncoding, "WKB") ||
    5843         319 :                  EQUAL(pszReqGeomEncoding, "")))
    5844             :             {
    5845         383 :                 const int iGeomField = fieldDesc[i].nIdx;
    5846         383 :                 const char *pszFormat = out_schema->children[j]->format;
    5847         383 :                 if (m_aeGeomEncoding[iGeomField] == OGRArrowGeomEncoding::WKB &&
    5848         695 :                     !out_schema->children[j]->metadata &&
    5849         312 :                     (strcmp(pszFormat, "z") == 0 ||
    5850           1 :                      strcmp(pszFormat, "Z") == 0))
    5851             :                 {
    5852             :                     const auto poGeomFieldDefn =
    5853         312 :                         m_poFeatureDefn->GetGeomFieldDefn(iGeomField);
    5854             :                     // Set ARROW:extension:name = ogc:wkb
    5855         312 :                     auto poSchema = CreateSchemaForWKBGeometryColumn(
    5856             :                         poGeomFieldDefn, pszFormat, pszExtensionName);
    5857         312 :                     out_schema->children[i]->release(out_schema->children[i]);
    5858         312 :                     *(out_schema->children[j]) = *poSchema;
    5859         312 :                     CPLFree(poSchema);
    5860             :                 }
    5861             :             }
    5862             : 
    5863       17640 :             ++j;
    5864             :         }
    5865             :     }
    5866             : 
    5867         388 :     out_schema->n_children = j;
    5868             : 
    5869         388 :     OverrideArrowRelease(m_poArrowDS, out_schema);
    5870             : 
    5871         388 :     return 0;
    5872             : }
    5873             : 
    5874             : /************************************************************************/
    5875             : /*                         GetNextArrowArray()                          */
    5876             : /************************************************************************/
    5877             : 
    5878         606 : inline int OGRArrowLayer::GetNextArrowArray(struct ArrowArrayStream *stream,
    5879             :                                             struct ArrowArray *out_array)
    5880             : {
    5881         606 :     if (m_bUseRecordBatchBaseImplementation)
    5882         224 :         return OGRLayer::GetNextArrowArray(stream, out_array);
    5883             : 
    5884             :     while (true)
    5885             :     {
    5886         400 :         if (m_bEOF)
    5887             :         {
    5888          10 :             memset(out_array, 0, sizeof(*out_array));
    5889         177 :             return 0;
    5890             :         }
    5891             : 
    5892         390 :         if (m_poBatch == nullptr || m_nIdxInBatch == m_poBatch->num_rows())
    5893             :         {
    5894         365 :             if (!ReadNextBatch())
    5895             :             {
    5896         167 :                 if (m_poAttrQuery || m_poFilterGeom)
    5897             :                 {
    5898          97 :                     InvalidateCachedBatches();
    5899             :                 }
    5900         167 :                 m_bEOF = true;
    5901         167 :                 memset(out_array, 0, sizeof(*out_array));
    5902         167 :                 return 0;
    5903             :             }
    5904             :         }
    5905             : 
    5906         223 :         const bool bNeedsPostFilter =
    5907         335 :             (m_poAttrQuery && !m_bBaseArrowIgnoreAttributeFilter) ||
    5908         112 :             (m_poFilterGeom && !m_bBaseArrowIgnoreSpatialFilter);
    5909             : 
    5910             :         struct ArrowSchema schema;
    5911         223 :         memset(&schema, 0, sizeof(schema));
    5912         223 :         auto status = arrow::ExportRecordBatch(*m_poBatch, out_array, &schema);
    5913         223 :         m_nIdxInBatch = m_poBatch->num_rows();
    5914         223 :         if (!status.ok())
    5915             :         {
    5916           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5917             :                      "ExportRecordBatch() failed with %s",
    5918           0 :                      status.message().c_str());
    5919           0 :             return EIO;
    5920             :         }
    5921             : 
    5922             :         // Remove bounding box columns from exported array, or columns
    5923             :         // of unsupported data types that we voluntarily strip off.
    5924             :         const auto RemoveBBoxOrUnsupportedColumns =
    5925       32662 :             [out_array, &schema](const std::set<int> &oSetBBoxArrayIndex)
    5926             :         {
    5927         223 :             int j = 0;
    5928       10820 :             for (int i = 0; i < static_cast<int>(schema.n_children); ++i)
    5929             :             {
    5930       21144 :                 if (cpl::contains(oSetBBoxArrayIndex, i) ||
    5931       10547 :                     IsSilentlyIgnoredFormatForGetArrowSchemaArray(
    5932       10547 :                         schema.children[i]->format))
    5933             :                 {
    5934         126 :                     out_array->children[i]->release(out_array->children[i]);
    5935         126 :                     out_array->children[i] = nullptr;
    5936             : 
    5937         126 :                     schema.children[i]->release(schema.children[i]);
    5938         126 :                     schema.children[i] = nullptr;
    5939             :                 }
    5940             :                 else
    5941             :                 {
    5942       10471 :                     out_array->children[j] = out_array->children[i];
    5943       10471 :                     schema.children[j] = schema.children[i];
    5944       10471 :                     ++j;
    5945             :                 }
    5946             :             }
    5947         223 :             out_array->n_children = j;
    5948         223 :             schema.n_children = j;
    5949         223 :         };
    5950             : 
    5951         223 :         if (m_bIgnoredFields)
    5952             :         {
    5953         180 :             std::set<int> oSetBBoxArrayIndex;
    5954         127 :             for (const auto &iter : m_oMapGeomFieldIndexToGeomColBBOX)
    5955             :             {
    5956          37 :                 if (iter.second.iArrayIdx >= 0)
    5957          20 :                     oSetBBoxArrayIndex.insert(iter.second.iArrayIdx);
    5958             :             }
    5959          90 :             RemoveBBoxOrUnsupportedColumns(oSetBBoxArrayIndex);
    5960             :         }
    5961             :         else
    5962             :         {
    5963         133 :             RemoveBBoxOrUnsupportedColumns(m_oSetBBoxArrowColumns);
    5964             :         }
    5965             : 
    5966         223 :         if (EQUAL(m_aosArrowArrayStreamOptions.FetchNameValueDef(
    5967             :                       "GEOMETRY_ENCODING", ""),
    5968             :                   "WKB"))
    5969             :         {
    5970          40 :             const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount();
    5971          95 :             for (int i = 0; i < nGeomFieldCount; i++)
    5972             :             {
    5973             :                 const auto poGeomFieldDefn =
    5974          55 :                     m_poFeatureDefn->GetGeomFieldDefn(i);
    5975          55 :                 if (!poGeomFieldDefn->IsIgnored())
    5976             :                 {
    5977          47 :                     if (m_aeGeomEncoding[i] == OGRArrowGeomEncoding::WKT)
    5978             :                     {
    5979             :                         const int nArrayIdx =
    5980          18 :                             m_bIgnoredFields
    5981          26 :                                 ? m_anMapGeomFieldIndexToArrayIndex[i]
    5982           8 :                                 : m_anMapGeomFieldIndexToArrowColumn[i];
    5983          18 :                         auto sourceArray = out_array->children[nArrayIdx];
    5984             :                         auto targetArray =
    5985          18 :                             strcmp(schema.children[nArrayIdx]->format, "u") == 0
    5986          18 :                                 ? CreateWKBArrayFromWKTArray<uint32_t>(
    5987             :                                       sourceArray)
    5988           0 :                                 : CreateWKBArrayFromWKTArray<uint64_t>(
    5989          18 :                                       sourceArray);
    5990          18 :                         if (targetArray)
    5991             :                         {
    5992          18 :                             sourceArray->release(sourceArray);
    5993          18 :                             *(out_array->children[nArrayIdx]) = *targetArray;
    5994          18 :                             CPLFree(targetArray);
    5995             :                         }
    5996             :                         else
    5997             :                         {
    5998           0 :                             out_array->release(out_array);
    5999           0 :                             memset(out_array, 0, sizeof(*out_array));
    6000           0 :                             if (schema.release)
    6001           0 :                                 schema.release(&schema);
    6002           0 :                             return ENOMEM;
    6003             :                         }
    6004             :                     }
    6005          29 :                     else if (m_aeGeomEncoding[i] != OGRArrowGeomEncoding::WKB)
    6006             :                     {
    6007             :                         // Shouldn't happen if UseRecordBatchBaseImplementation()
    6008             :                         // is up to date
    6009           0 :                         CPLAssert(false);
    6010             :                     }
    6011             :                 }
    6012             :             }
    6013             :         }
    6014             : 
    6015         223 :         if (schema.release)
    6016         223 :             schema.release(&schema);
    6017             : 
    6018         223 :         OverrideArrowRelease(m_poArrowDS, out_array);
    6019             : 
    6020         223 :         const auto nFeatureIdxCur = m_nFeatureIdx;
    6021             :         // TODO: We likely have an issue regarding FIDs based on m_nFeatureIdx
    6022             :         // when m_iFIDArrowColumn < 0, only a subset of row groups is
    6023             :         // selected, and this batch goes across non consecutive row groups.
    6024        1123 :         for (int64_t i = 0; i < m_nIdxInBatch; ++i)
    6025         900 :             IncrFeatureIdx();
    6026             : 
    6027         223 :         if (bNeedsPostFilter)
    6028             :         {
    6029         130 :             CPLStringList aosOptions;
    6030         130 :             if (m_iFIDArrowColumn < 0)
    6031             :                 aosOptions.SetNameValue(
    6032             :                     "BASE_SEQUENTIAL_FID",
    6033             :                     CPLSPrintf(CPL_FRMT_GIB,
    6034         122 :                                static_cast<GIntBig>(nFeatureIdxCur)));
    6035             : 
    6036             :             // If there might be more than one record batch, it is more
    6037             :             // prudent to clone the array before modifying it.
    6038         130 :             if (nFeatureIdxCur > 0 || !TestCapability(OLCFastFeatureCount) ||
    6039           0 :                 out_array->length < GetFeatureCount(false))
    6040             :             {
    6041             :                 struct ArrowArray new_array;
    6042         130 :                 if (!OGRCloneArrowArray(&m_sCachedSchema, out_array,
    6043             :                                         &new_array))
    6044             :                 {
    6045           0 :                     if (out_array->release)
    6046           0 :                         out_array->release(out_array);
    6047           0 :                     memset(out_array, 0, sizeof(*out_array));
    6048           0 :                     return ENOMEM;
    6049             :                 }
    6050         130 :                 if (out_array->release)
    6051         130 :                     out_array->release(out_array);
    6052         130 :                 memcpy(out_array, &new_array, sizeof(new_array));
    6053             :             }
    6054             : 
    6055         130 :             PostFilterArrowArray(&m_sCachedSchema, out_array,
    6056         130 :                                  aosOptions.List());
    6057         130 :             if (out_array->length == 0)
    6058             :             {
    6059          18 :                 if (out_array->release)
    6060          18 :                     out_array->release(out_array);
    6061          18 :                 memset(out_array, 0, sizeof(*out_array));
    6062             :                 // If there are no records after filtering, start again
    6063             :                 // with a new batch
    6064          18 :                 continue;
    6065             :             }
    6066             :         }
    6067             : 
    6068         205 :         break;
    6069          18 :     }
    6070             : 
    6071         205 :     return 0;
    6072             : }
    6073             : 
    6074             : /************************************************************************/
    6075             : /*                      OGRArrowLayerAppendBuffer                       */
    6076             : /************************************************************************/
    6077             : 
    6078             : class OGRArrowLayerAppendBuffer : public OGRAppendBuffer
    6079             : {
    6080             :   public:
    6081          18 :     OGRArrowLayerAppendBuffer(struct ArrowArray *targetArrayIn,
    6082             :                               size_t nInitialCapacityIn)
    6083          18 :         : m_psTargetArray(targetArrayIn)
    6084             :     {
    6085          18 :         m_nCapacity = nInitialCapacityIn;
    6086          18 :         m_pRawBuffer = const_cast<void *>(m_psTargetArray->buffers[2]);
    6087          18 :     }
    6088             : 
    6089             :   protected:
    6090           4 :     bool Grow(size_t nItemSize) override
    6091             :     {
    6092           4 :         constexpr uint32_t MAX_SIZE_SINT32 =
    6093             :             static_cast<uint32_t>(std::numeric_limits<int32_t>::max());
    6094           4 :         if (nItemSize > MAX_SIZE_SINT32 - m_nSize)
    6095             :         {
    6096           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Too large WKT content");
    6097           0 :             return false;
    6098             :         }
    6099           4 :         size_t nNewCapacity = m_nSize + nItemSize;
    6100           4 :         CPLAssert(m_nCapacity <= MAX_SIZE_SINT32);
    6101             :         const size_t nDoubleCapacity =
    6102           4 :             std::min<size_t>(MAX_SIZE_SINT32, 2 * m_nCapacity);
    6103           4 :         if (nNewCapacity < nDoubleCapacity)
    6104           4 :             nNewCapacity = nDoubleCapacity;
    6105           4 :         CPLAssert(nNewCapacity <= MAX_SIZE_SINT32);
    6106           4 :         void *newBuffer = VSI_MALLOC_ALIGNED_AUTO_VERBOSE(nNewCapacity);
    6107           4 :         if (newBuffer == nullptr)
    6108             :         {
    6109           0 :             return false;
    6110             :         }
    6111           4 :         m_nCapacity = nNewCapacity;
    6112           4 :         memcpy(newBuffer, m_pRawBuffer, m_nSize);
    6113           4 :         VSIFreeAligned(m_pRawBuffer);
    6114           4 :         m_pRawBuffer = newBuffer;
    6115           4 :         m_psTargetArray->buffers[2] = m_pRawBuffer;
    6116           4 :         return true;
    6117             :     }
    6118             : 
    6119             :   private:
    6120             :     struct ArrowArray *m_psTargetArray;
    6121             : 
    6122             :     OGRArrowLayerAppendBuffer(const OGRArrowLayerAppendBuffer &) = delete;
    6123             :     OGRArrowLayerAppendBuffer &
    6124             :     operator=(const OGRArrowLayerAppendBuffer &) = delete;
    6125             : };
    6126             : 
    6127             : /************************************************************************/
    6128             : /*                     CreateWKBArrayFromWKTArray()                     */
    6129             : /************************************************************************/
    6130             : 
    6131             : template <typename SourceOffset>
    6132             : inline struct ArrowArray *
    6133          18 : OGRArrowLayer::CreateWKBArrayFromWKTArray(const struct ArrowArray *sourceArray)
    6134             : {
    6135          18 :     CPLAssert(sourceArray->n_buffers == 3);
    6136          18 :     CPLAssert(sourceArray->buffers[1] != nullptr);
    6137          18 :     CPLAssert(sourceArray->buffers[2] != nullptr);
    6138             : 
    6139          18 :     const size_t nLength = static_cast<size_t>(sourceArray->length);
    6140             :     auto targetArray = static_cast<struct ArrowArray *>(
    6141          18 :         CPLCalloc(1, sizeof(struct ArrowArray)));
    6142          18 :     targetArray->release = OGRLayer::ReleaseArray;
    6143          18 :     targetArray->length = nLength;
    6144             : 
    6145          18 :     targetArray->n_buffers = 3;
    6146          18 :     targetArray->buffers =
    6147          18 :         static_cast<const void **>(CPLCalloc(3, sizeof(void *)));
    6148             : 
    6149             :     // Allocate validity map buffer if needed
    6150          18 :     const auto sourceNull =
    6151          18 :         static_cast<const uint8_t *>(sourceArray->buffers[0]);
    6152          18 :     const size_t nOffset = static_cast<size_t>(sourceArray->offset);
    6153          18 :     uint8_t *targetNull = nullptr;
    6154          18 :     if (sourceArray->null_count && sourceNull)
    6155             :     {
    6156           6 :         targetArray->buffers[0] =
    6157           3 :             VSI_MALLOC_ALIGNED_AUTO_VERBOSE((nLength + 7) / 8);
    6158           3 :         if (targetArray->buffers[0])
    6159             :         {
    6160           3 :             targetArray->null_count = sourceArray->null_count;
    6161           3 :             targetNull = static_cast<uint8_t *>(
    6162           3 :                 const_cast<void *>(targetArray->buffers[0]));
    6163           3 :             if (nOffset == 0)
    6164             :             {
    6165           3 :                 memcpy(targetNull, sourceNull, (nLength + 7) / 8);
    6166             :             }
    6167             :             else
    6168             :             {
    6169           0 :                 memset(targetNull, 0, (nLength + 7) / 8);
    6170           0 :                 for (size_t i = 0; i < nLength; ++i)
    6171             :                 {
    6172           0 :                     if ((sourceNull[(i + nOffset) / 8] >> ((i + nOffset) % 8)) &
    6173             :                         1)
    6174           0 :                         targetNull[i / 8] |= (1 << (i % 8));
    6175             :                 }
    6176             :             }
    6177             :         }
    6178             :     }
    6179             : 
    6180             :     // Allocate offset buffer
    6181          36 :     targetArray->buffers[1] =
    6182          18 :         VSI_MALLOC_ALIGNED_AUTO_VERBOSE(sizeof(uint32_t) * (1 + nLength));
    6183             : 
    6184             :     // Allocate data (WKB) buffer
    6185          18 :     constexpr size_t DEFAULT_WKB_SIZE = 100;
    6186          18 :     uint32_t nInitialCapacity = static_cast<uint32_t>(std::min<size_t>(
    6187          18 :         std::numeric_limits<int32_t>::max(), DEFAULT_WKB_SIZE * nLength));
    6188          18 :     targetArray->buffers[2] = VSI_MALLOC_ALIGNED_AUTO_VERBOSE(nInitialCapacity);
    6189             : 
    6190             :     // Check buffers have been allocated
    6191          18 :     if ((sourceArray->null_count && sourceNull && !targetNull) ||
    6192          18 :         targetArray->buffers[1] == nullptr ||
    6193          18 :         targetArray->buffers[2] == nullptr)
    6194             :     {
    6195           0 :         targetArray->release(targetArray);
    6196           0 :         return nullptr;
    6197             :     }
    6198             : 
    6199          36 :     OGRArrowLayerAppendBuffer oOGRAppendBuffer(targetArray, nInitialCapacity);
    6200          18 :     OGRWKTToWKBTranslator oTranslator(oOGRAppendBuffer);
    6201             : 
    6202          18 :     const auto sourceOffsets =
    6203          18 :         static_cast<const SourceOffset *>(sourceArray->buffers[1]) + nOffset;
    6204          18 :     auto sourceBytes =
    6205          18 :         static_cast<char *>(const_cast<void *>(sourceArray->buffers[2]));
    6206          18 :     auto targetOffsets =
    6207          18 :         static_cast<uint32_t *>(const_cast<void *>(targetArray->buffers[1]));
    6208         159 :     for (size_t i = 0; i < nLength; ++i)
    6209             :     {
    6210         141 :         targetOffsets[i] = static_cast<uint32_t>(oOGRAppendBuffer.GetSize());
    6211             : 
    6212         141 :         if (targetNull && ((targetNull[i / 8] >> (i % 8)) & 1) == 0)
    6213             :         {
    6214           3 :             continue;
    6215             :         }
    6216             : 
    6217         276 :         const size_t nWKBSize = oTranslator.TranslateWKT(
    6218         138 :             sourceBytes + sourceOffsets[i],
    6219         138 :             static_cast<size_t>(sourceOffsets[i + 1] - sourceOffsets[i]),
    6220         138 :             sourceOffsets[i + 1] < sourceOffsets[nLength]);
    6221         138 :         if (nWKBSize == static_cast<size_t>(-1))
    6222             :         {
    6223           0 :             targetArray->release(targetArray);
    6224           0 :             return nullptr;
    6225             :         }
    6226             :     }
    6227          18 :     targetOffsets[nLength] = static_cast<uint32_t>(oOGRAppendBuffer.GetSize());
    6228             : 
    6229          18 :     return targetArray;
    6230             : }
    6231             : 
    6232             : /************************************************************************/
    6233             : /*                           TestCapability()                           */
    6234             : /************************************************************************/
    6235             : 
    6236         913 : inline int OGRArrowLayer::TestCapability(const char *pszCap) const
    6237             : {
    6238             : 
    6239         913 :     if (EQUAL(pszCap, OLCStringsAsUTF8))
    6240         505 :         return true;
    6241             : 
    6242         589 :     else if (EQUAL(pszCap, OLCFastGetArrowStream) &&
    6243         181 :              !UseRecordBatchBaseImplementation())
    6244             :     {
    6245         181 :         return true;
    6246             :     }
    6247             : 
    6248         227 :     if (EQUAL(pszCap, OLCFastGetExtent))
    6249             :     {
    6250          31 :         OGREnvelope sEnvelope;
    6251          48 :         for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); i++)
    6252             :         {
    6253          31 :             if (!FastGetExtent(i, &sEnvelope))
    6254          14 :                 return false;
    6255             :         }
    6256          17 :         return true;
    6257             :     }
    6258             : 
    6259         196 :     if (EQUAL(pszCap, OLCFastGetExtent3D))
    6260             :     {
    6261          19 :         OGREnvelope3D sEnvelope;
    6262          20 :         for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); i++)
    6263             :         {
    6264          19 :             if (!FastGetExtent3D(i, &sEnvelope))
    6265          18 :                 return false;
    6266             :         }
    6267           1 :         return true;
    6268             :     }
    6269             : 
    6270         177 :     return false;
    6271             : }
    6272             : 
    6273             : #if defined(__clang__)
    6274             : #pragma clang diagnostic pop
    6275             : #endif
    6276             : 
    6277             : #endif /* OGARROWLAYER_HPP_INCLUDED */

Generated by: LCOV version 1.14