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 3169 88.1 %
Date: 2026-01-07 04:16:14 Functions: 129 136 94.9 %

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

Generated by: LCOV version 1.14