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: 2660 2994 88.8 %
Date: 2024-05-13 13:33:37 Functions: 111 118 94.1 %

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

Generated by: LCOV version 1.14