LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/parquet - ogrparquetlayer.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1290 1393 92.6 %
Date: 2024-05-14 23:54:21 Functions: 52 52 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Parquet Translator
       4             :  * Purpose:  Implements OGRParquetDriver.
       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             : #include "cpl_json.h"
      30             : #include "cpl_time.h"
      31             : #include "cpl_multiproc.h"
      32             : #include "gdal_pam.h"
      33             : #include "ogrsf_frmts.h"
      34             : #include "ogr_p.h"
      35             : 
      36             : #include <algorithm>
      37             : #include <cinttypes>
      38             : #include <limits>
      39             : #include <map>
      40             : #include <set>
      41             : #include <utility>
      42             : 
      43             : #include "ogr_parquet.h"
      44             : 
      45             : #include "../arrow_common/ograrrowlayer.hpp"
      46             : #include "../arrow_common/ograrrowdataset.hpp"
      47             : 
      48             : /************************************************************************/
      49             : /*                    OGRParquetLayerBase()                             */
      50             : /************************************************************************/
      51             : 
      52         681 : OGRParquetLayerBase::OGRParquetLayerBase(OGRParquetDataset *poDS,
      53             :                                          const char *pszLayerName,
      54         681 :                                          CSLConstList papszOpenOptions)
      55             :     : OGRArrowLayer(poDS, pszLayerName), m_poDS(poDS),
      56             :       m_aosGeomPossibleNames(CSLTokenizeString2(
      57             :           CSLFetchNameValueDef(papszOpenOptions, "GEOM_POSSIBLE_NAMES",
      58             :                                "geometry,wkb_geometry,wkt_geometry"),
      59             :           ",", 0)),
      60         681 :       m_osCRS(CSLFetchNameValueDef(papszOpenOptions, "CRS", ""))
      61             : {
      62         681 : }
      63             : 
      64             : /************************************************************************/
      65             : /*                           GetDataset()                               */
      66             : /************************************************************************/
      67             : 
      68          18 : GDALDataset *OGRParquetLayerBase::GetDataset()
      69             : {
      70          18 :     return m_poDS;
      71             : }
      72             : 
      73             : /************************************************************************/
      74             : /*                           ResetReading()                             */
      75             : /************************************************************************/
      76             : 
      77        3389 : void OGRParquetLayerBase::ResetReading()
      78             : {
      79        3389 :     if (m_iRecordBatch != 0)
      80             :     {
      81        3242 :         m_poRecordBatchReader.reset();
      82             :     }
      83        3389 :     OGRArrowLayer::ResetReading();
      84        3389 : }
      85             : 
      86             : /************************************************************************/
      87             : /*                          LoadGeoMetadata()                           */
      88             : /************************************************************************/
      89             : 
      90         681 : void OGRParquetLayerBase::LoadGeoMetadata(
      91             :     const std::shared_ptr<const arrow::KeyValueMetadata> &kv_metadata)
      92             : {
      93         681 :     if (kv_metadata && kv_metadata->Contains("geo"))
      94             :     {
      95        1280 :         auto geo = kv_metadata->Get("geo");
      96         640 :         if (geo.ok())
      97             :         {
      98         640 :             CPLDebug("PARQUET", "geo = %s", geo->c_str());
      99        1280 :             CPLJSONDocument oDoc;
     100         640 :             if (oDoc.LoadMemory(*geo))
     101             :             {
     102        1278 :                 auto oRoot = oDoc.GetRoot();
     103        1917 :                 const auto osVersion = oRoot.GetString("version");
     104        1573 :                 if (osVersion != "0.1.0" && osVersion != "0.2.0" &&
     105        1401 :                     osVersion != "0.3.0" && osVersion != "0.4.0" &&
     106        1399 :                     osVersion != "1.0.0-beta.1" && osVersion != "1.0.0-rc.1" &&
     107        1572 :                     osVersion != "1.0.0" && osVersion != "1.1.0")
     108             :                 {
     109           1 :                     CPLDebug(
     110             :                         "PARQUET",
     111             :                         "version = %s not explicitly handled by the driver",
     112             :                         osVersion.c_str());
     113             :                 }
     114             : 
     115        1917 :                 auto oColumns = oRoot.GetObj("columns");
     116         639 :                 if (oColumns.IsValid())
     117             :                 {
     118        1293 :                     for (const auto &oColumn : oColumns.GetChildren())
     119             :                     {
     120         655 :                         m_oMapGeometryColumns[oColumn.GetName()] = oColumn;
     121             :                     }
     122             :                 }
     123             :             }
     124             :             else
     125             :             {
     126           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
     127             :                          "Cannot parse 'geo' metadata");
     128             :             }
     129             :         }
     130             :     }
     131         681 : }
     132             : 
     133             : /************************************************************************/
     134             : /*                   ParseGeometryColumnCovering()                      */
     135             : /************************************************************************/
     136             : 
     137             : //! Parse bounding box column definition
     138        1296 : static bool ParseGeometryColumnCovering(const CPLJSONObject &oJSONDef,
     139             :                                         std::string &osBBOXColumn,
     140             :                                         std::string &osXMin,
     141             :                                         std::string &osYMin,
     142             :                                         std::string &osXMax,
     143             :                                         std::string &osYMax)
     144             : {
     145        3888 :     const auto oCovering = oJSONDef["covering"];
     146        1908 :     if (oCovering.IsValid() &&
     147         612 :         oCovering.GetType() == CPLJSONObject::Type::Object)
     148             :     {
     149        1224 :         const auto oBBOX = oCovering["bbox"];
     150         612 :         if (oBBOX.IsValid() && oBBOX.GetType() == CPLJSONObject::Type::Object)
     151             :         {
     152        1224 :             const auto oXMin = oBBOX["xmin"];
     153        1224 :             const auto oYMin = oBBOX["ymin"];
     154        1224 :             const auto oXMax = oBBOX["xmax"];
     155        1224 :             const auto oYMax = oBBOX["ymax"];
     156        1224 :             if (oXMin.IsValid() && oYMin.IsValid() && oXMax.IsValid() &&
     157         612 :                 oYMax.IsValid() &&
     158         612 :                 oXMin.GetType() == CPLJSONObject::Type::Array &&
     159         612 :                 oYMin.GetType() == CPLJSONObject::Type::Array &&
     160        1836 :                 oXMax.GetType() == CPLJSONObject::Type::Array &&
     161         612 :                 oYMax.GetType() == CPLJSONObject::Type::Array)
     162             :             {
     163         612 :                 const auto osXMinArray = oXMin.ToArray();
     164         612 :                 const auto osYMinArray = oYMin.ToArray();
     165         612 :                 const auto osXMaxArray = oXMax.ToArray();
     166         612 :                 const auto osYMaxArray = oYMax.ToArray();
     167         612 :                 if (osXMinArray.Size() == 2 && osYMinArray.Size() == 2 &&
     168         612 :                     osXMaxArray.Size() == 2 && osYMaxArray.Size() == 2 &&
     169        1224 :                     osXMinArray[0].GetType() == CPLJSONObject::Type::String &&
     170        1224 :                     osXMinArray[1].GetType() == CPLJSONObject::Type::String &&
     171        1224 :                     osYMinArray[0].GetType() == CPLJSONObject::Type::String &&
     172        1224 :                     osYMinArray[1].GetType() == CPLJSONObject::Type::String &&
     173        1224 :                     osXMaxArray[0].GetType() == CPLJSONObject::Type::String &&
     174        1224 :                     osXMaxArray[1].GetType() == CPLJSONObject::Type::String &&
     175        1224 :                     osYMaxArray[0].GetType() == CPLJSONObject::Type::String &&
     176        1836 :                     osYMaxArray[1].GetType() == CPLJSONObject::Type::String &&
     177        1836 :                     osXMinArray[0].ToString() == osYMinArray[0].ToString() &&
     178        2448 :                     osXMinArray[0].ToString() == osXMaxArray[0].ToString() &&
     179        1224 :                     osXMinArray[0].ToString() == osYMaxArray[0].ToString())
     180             :                 {
     181         612 :                     osBBOXColumn = osXMinArray[0].ToString();
     182         612 :                     osXMin = osXMinArray[1].ToString();
     183         612 :                     osYMin = osYMinArray[1].ToString();
     184         612 :                     osXMax = osXMaxArray[1].ToString();
     185         612 :                     osYMax = osYMaxArray[1].ToString();
     186         612 :                     return true;
     187             :                 }
     188             :             }
     189             :         }
     190             :     }
     191         684 :     return false;
     192             : }
     193             : 
     194             : /************************************************************************/
     195             : /*                      DealWithGeometryColumn()                        */
     196             : /************************************************************************/
     197             : 
     198       15041 : bool OGRParquetLayerBase::DealWithGeometryColumn(
     199             :     int iFieldIdx, const std::shared_ptr<arrow::Field> &field,
     200             :     std::function<OGRwkbGeometryType(void)> computeGeometryTypeFun)
     201             : {
     202       30082 :     const auto &field_kv_metadata = field->metadata();
     203       15041 :     std::string osExtensionName;
     204       15041 :     if (field_kv_metadata)
     205             :     {
     206           6 :         auto extension_name = field_kv_metadata->Get("ARROW:extension:name");
     207           3 :         if (extension_name.ok())
     208             :         {
     209           3 :             osExtensionName = *extension_name;
     210             :         }
     211             : #ifdef DEBUG
     212           3 :         CPLDebug("PARQUET", "Metadata field %s:", field->name().c_str());
     213           8 :         for (const auto &keyValue : field_kv_metadata->sorted_pairs())
     214             :         {
     215           5 :             CPLDebug("PARQUET", "  %s = %s", keyValue.first.c_str(),
     216             :                      keyValue.second.c_str());
     217             :         }
     218             : #endif
     219             :     }
     220             : 
     221       15041 :     bool bRegularField = true;
     222       15041 :     auto oIter = m_oMapGeometryColumns.find(field->name());
     223       15041 :     if (oIter != m_oMapGeometryColumns.end() ||
     224       29427 :         STARTS_WITH(osExtensionName.c_str(), "ogc.") ||
     225       14386 :         STARTS_WITH(osExtensionName.c_str(), "geoarrow."))
     226             :     {
     227        1312 :         CPLJSONObject oJSONDef;
     228         656 :         if (oIter != m_oMapGeometryColumns.end())
     229         655 :             oJSONDef = oIter->second;
     230        1968 :         auto osEncoding = oJSONDef.GetString("encoding");
     231         656 :         if (osEncoding.empty() && !osExtensionName.empty())
     232           1 :             osEncoding = osExtensionName;
     233             : 
     234         656 :         OGRwkbGeometryType eGeomType = wkbUnknown;
     235         656 :         auto eGeomEncoding = OGRArrowGeomEncoding::WKB;
     236         656 :         if (IsValidGeometryEncoding(field, osEncoding,
     237        1312 :                                     oIter != m_oMapGeometryColumns.end(),
     238             :                                     eGeomType, eGeomEncoding))
     239             :         {
     240         656 :             bRegularField = false;
     241        1312 :             OGRGeomFieldDefn oField(field->name().c_str(), wkbUnknown);
     242             : 
     243        1968 :             auto oCRS = oJSONDef["crs"];
     244         656 :             OGRSpatialReference *poSRS = nullptr;
     245         656 :             if (!oCRS.IsValid())
     246             :             {
     247          34 :                 if (!m_oMapGeometryColumns.empty())
     248             :                 {
     249             :                     // WGS 84 is implied if no crs member is found.
     250          33 :                     poSRS = new OGRSpatialReference();
     251          33 :                     poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     252          33 :                     poSRS->importFromEPSG(4326);
     253             :                 }
     254             :             }
     255         622 :             else if (oCRS.GetType() == CPLJSONObject::Type::String)
     256             :             {
     257         513 :                 const auto osWKT = oCRS.ToString();
     258         171 :                 poSRS = new OGRSpatialReference();
     259         171 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     260             : 
     261         171 :                 if (poSRS->importFromWkt(osWKT.c_str()) != OGRERR_NONE)
     262             :                 {
     263           0 :                     poSRS->Release();
     264           0 :                     poSRS = nullptr;
     265             :                 }
     266             :             }
     267         451 :             else if (oCRS.GetType() == CPLJSONObject::Type::Object)
     268             :             {
     269             :                 // CRS encoded as PROJJSON (extension)
     270          57 :                 const auto oType = oCRS["type"];
     271          38 :                 if (oType.IsValid() &&
     272          19 :                     oType.GetType() == CPLJSONObject::Type::String)
     273             :                 {
     274          57 :                     const auto osType = oType.ToString();
     275          19 :                     if (osType.find("CRS") != std::string::npos)
     276             :                     {
     277          19 :                         poSRS = new OGRSpatialReference();
     278          19 :                         poSRS->SetAxisMappingStrategy(
     279             :                             OAMS_TRADITIONAL_GIS_ORDER);
     280             : 
     281          19 :                         if (poSRS->SetFromUserInput(oCRS.ToString().c_str()) !=
     282             :                             OGRERR_NONE)
     283             :                         {
     284           0 :                             poSRS->Release();
     285           0 :                             poSRS = nullptr;
     286             :                         }
     287             :                     }
     288             :                 }
     289             :             }
     290             : 
     291         656 :             if (poSRS)
     292             :             {
     293         223 :                 const double dfCoordEpoch = oJSONDef.GetDouble("epoch");
     294         223 :                 if (dfCoordEpoch > 0)
     295           4 :                     poSRS->SetCoordinateEpoch(dfCoordEpoch);
     296             : 
     297         223 :                 oField.SetSpatialRef(poSRS);
     298             : 
     299         223 :                 poSRS->Release();
     300             :             }
     301             : 
     302         656 :             if (!m_osCRS.empty())
     303             :             {
     304           0 :                 poSRS = new OGRSpatialReference();
     305           0 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     306           0 :                 if (poSRS->SetFromUserInput(m_osCRS.c_str()) == OGRERR_NONE)
     307             :                 {
     308           0 :                     oField.SetSpatialRef(poSRS);
     309             :                 }
     310           0 :                 poSRS->Release();
     311             :             }
     312             : 
     313         656 :             if (oJSONDef.GetString("edges") == "spherical")
     314             :             {
     315           2 :                 SetMetadataItem("EDGES", "SPHERICAL");
     316             :             }
     317             : 
     318             :             // m_aeGeomEncoding be filled before calling
     319             :             // ComputeGeometryColumnType()
     320         656 :             m_aeGeomEncoding.push_back(eGeomEncoding);
     321         656 :             if (eGeomType == wkbUnknown)
     322             :             {
     323             :                 // geometry_types since 1.0.0-beta1. Was geometry_type
     324             :                 // before
     325        1044 :                 auto oType = oJSONDef.GetObj("geometry_types");
     326         348 :                 if (!oType.IsValid())
     327         174 :                     oType = oJSONDef.GetObj("geometry_type");
     328         348 :                 if (oType.GetType() == CPLJSONObject::Type::String)
     329             :                 {
     330             :                     // string is no longer valid since 1.0.0-beta1
     331           0 :                     const auto osType = oType.ToString();
     332           0 :                     if (osType != "Unknown")
     333           0 :                         eGeomType = GetGeometryTypeFromString(osType);
     334             :                 }
     335         348 :                 else if (oType.GetType() == CPLJSONObject::Type::Array)
     336             :                 {
     337         348 :                     const auto oTypeArray = oType.ToArray();
     338         174 :                     if (oTypeArray.Size() == 1)
     339             :                     {
     340          74 :                         eGeomType =
     341          74 :                             GetGeometryTypeFromString(oTypeArray[0].ToString());
     342             :                     }
     343         100 :                     else if (oTypeArray.Size() > 1)
     344             :                     {
     345             :                         const auto PromoteToCollection =
     346         266 :                             [](OGRwkbGeometryType eType)
     347             :                         {
     348         266 :                             if (eType == wkbPoint)
     349          41 :                                 return wkbMultiPoint;
     350         225 :                             if (eType == wkbLineString)
     351          36 :                                 return wkbMultiLineString;
     352         189 :                             if (eType == wkbPolygon)
     353          49 :                                 return wkbMultiPolygon;
     354         140 :                             return eType;
     355             :                         };
     356          50 :                         bool bMixed = false;
     357          50 :                         bool bHasMulti = false;
     358          50 :                         bool bHasZ = false;
     359          50 :                         bool bHasM = false;
     360             :                         const auto eFirstType =
     361          50 :                             OGR_GT_Flatten(GetGeometryTypeFromString(
     362         100 :                                 oTypeArray[0].ToString()));
     363             :                         const auto eFirstTypeCollection =
     364          50 :                             PromoteToCollection(eFirstType);
     365         142 :                         for (int i = 0; i < oTypeArray.Size(); ++i)
     366             :                         {
     367         124 :                             const auto eThisGeom = GetGeometryTypeFromString(
     368         248 :                                 oTypeArray[i].ToString());
     369         124 :                             if (PromoteToCollection(OGR_GT_Flatten(
     370         124 :                                     eThisGeom)) != eFirstTypeCollection)
     371             :                             {
     372          32 :                                 bMixed = true;
     373          32 :                                 break;
     374             :                             }
     375          92 :                             bHasZ |= OGR_GT_HasZ(eThisGeom) != FALSE;
     376          92 :                             bHasM |= OGR_GT_HasM(eThisGeom) != FALSE;
     377          92 :                             bHasMulti |=
     378          92 :                                 (PromoteToCollection(OGR_GT_Flatten(
     379          92 :                                      eThisGeom)) == OGR_GT_Flatten(eThisGeom));
     380             :                         }
     381          50 :                         if (!bMixed)
     382             :                         {
     383          18 :                             if (eFirstTypeCollection == wkbMultiPolygon ||
     384             :                                 eFirstTypeCollection == wkbMultiLineString)
     385             :                             {
     386          17 :                                 if (bHasMulti)
     387          17 :                                     eGeomType = OGR_GT_SetModifier(
     388             :                                         eFirstTypeCollection, bHasZ, bHasM);
     389             :                                 else
     390           0 :                                     eGeomType = OGR_GT_SetModifier(
     391             :                                         eFirstType, bHasZ, bHasM);
     392             :                             }
     393             :                         }
     394             :                     }
     395             :                 }
     396         174 :                 else if (CPLTestBool(CPLGetConfigOption(
     397             :                              "OGR_PARQUET_COMPUTE_GEOMETRY_TYPE", "YES")))
     398             :                 {
     399         174 :                     eGeomType = computeGeometryTypeFun();
     400             :                 }
     401             :             }
     402             : 
     403         656 :             oField.SetType(eGeomType);
     404         656 :             oField.SetNullable(field->nullable());
     405         656 :             m_poFeatureDefn->AddGeomFieldDefn(&oField);
     406         656 :             m_anMapGeomFieldIndexToArrowColumn.push_back(iFieldIdx);
     407             :         }
     408             :     }
     409             : 
     410             :     // Try to autodetect a (WKB) geometry column from the GEOM_POSSIBLE_NAMES
     411             :     // open option
     412       15200 :     if (osExtensionName.empty() && m_oMapGeometryColumns.empty() &&
     413         159 :         m_aosGeomPossibleNames.FindString(field->name().c_str()) >= 0)
     414             :     {
     415          22 :         std::shared_ptr<arrow::DataType> fieldType = field->type();
     416          11 :         auto fieldTypeId = fieldType->id();
     417          11 :         if (fieldTypeId == arrow::Type::BINARY ||
     418             :             fieldTypeId == arrow::Type::LARGE_BINARY)
     419             :         {
     420           5 :             CPLDebug("PARQUET",
     421             :                      "Field %s detected as likely WKB geometry field",
     422           5 :                      field->name().c_str());
     423           5 :             bRegularField = false;
     424           5 :             m_aeGeomEncoding.push_back(OGRArrowGeomEncoding::WKB);
     425             :         }
     426           0 :         else if ((fieldTypeId == arrow::Type::STRING ||
     427          16 :                   fieldTypeId == arrow::Type::LARGE_STRING) &&
     428          10 :                  (field->name().find("wkt") != std::string::npos ||
     429           4 :                   field->name().find("WKT") != std::string::npos))
     430             :         {
     431           2 :             CPLDebug("PARQUET",
     432             :                      "Field %s detected as likely WKT geometry field",
     433           2 :                      field->name().c_str());
     434           2 :             bRegularField = false;
     435           2 :             m_aeGeomEncoding.push_back(OGRArrowGeomEncoding::WKT);
     436             :         }
     437          11 :         if (!bRegularField)
     438             :         {
     439          14 :             OGRGeomFieldDefn oField(field->name().c_str(), wkbUnknown);
     440           7 :             oField.SetNullable(field->nullable());
     441             : 
     442           7 :             if (!m_osCRS.empty())
     443             :             {
     444           2 :                 auto poSRS = new OGRSpatialReference();
     445           2 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     446           2 :                 if (poSRS->SetFromUserInput(m_osCRS.c_str()) == OGRERR_NONE)
     447             :                 {
     448           2 :                     oField.SetSpatialRef(poSRS);
     449             :                 }
     450           2 :                 poSRS->Release();
     451             :             }
     452             : 
     453           7 :             m_poFeatureDefn->AddGeomFieldDefn(&oField);
     454           7 :             m_anMapGeomFieldIndexToArrowColumn.push_back(iFieldIdx);
     455             :         }
     456             :     }
     457             : 
     458       30082 :     return !bRegularField;
     459             : }
     460             : 
     461             : /************************************************************************/
     462             : /*                         TestCapability()                             */
     463             : /************************************************************************/
     464             : 
     465         420 : int OGRParquetLayerBase::TestCapability(const char *pszCap)
     466             : {
     467         420 :     if (EQUAL(pszCap, OLCMeasuredGeometries))
     468          20 :         return true;
     469             : 
     470         400 :     if (EQUAL(pszCap, OLCFastSetNextByIndex))
     471           0 :         return true;
     472             : 
     473         400 :     return OGRArrowLayer::TestCapability(pszCap);
     474             : }
     475             : 
     476             : /************************************************************************/
     477             : /*                        OGRParquetLayer()                             */
     478             : /************************************************************************/
     479             : 
     480         660 : OGRParquetLayer::OGRParquetLayer(
     481             :     OGRParquetDataset *poDS, const char *pszLayerName,
     482             :     std::unique_ptr<parquet::arrow::FileReader> &&arrow_reader,
     483         660 :     CSLConstList papszOpenOptions)
     484             :     : OGRParquetLayerBase(poDS, pszLayerName, papszOpenOptions),
     485         660 :       m_poArrowReader(std::move(arrow_reader))
     486             : {
     487             :     const char *pszParquetBatchSize =
     488         660 :         CPLGetConfigOption("OGR_PARQUET_BATCH_SIZE", nullptr);
     489         660 :     if (pszParquetBatchSize)
     490           5 :         m_poArrowReader->set_batch_size(CPLAtoGIntBig(pszParquetBatchSize));
     491             : 
     492         660 :     const char *pszNumThreads = CPLGetConfigOption("GDAL_NUM_THREADS", nullptr);
     493         660 :     int nNumThreads = 0;
     494         660 :     if (pszNumThreads == nullptr)
     495         660 :         nNumThreads = std::min(4, CPLGetNumCPUs());
     496             :     else
     497           0 :         nNumThreads = EQUAL(pszNumThreads, "ALL_CPUS") ? CPLGetNumCPUs()
     498           0 :                                                        : atoi(pszNumThreads);
     499         660 :     if (nNumThreads > 1)
     500             :     {
     501         660 :         CPL_IGNORE_RET_VAL(arrow::SetCpuThreadPoolCapacity(nNumThreads));
     502         660 :         m_poArrowReader->set_use_threads(true);
     503             :     }
     504             : 
     505         660 :     EstablishFeatureDefn();
     506         660 :     CPLAssert(static_cast<int>(m_aeGeomEncoding.size()) ==
     507             :               m_poFeatureDefn->GetGeomFieldCount());
     508             : 
     509         660 :     m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
     510         660 : }
     511             : 
     512             : /************************************************************************/
     513             : /*                        EstablishFeatureDefn()                        */
     514             : /************************************************************************/
     515             : 
     516         660 : void OGRParquetLayer::EstablishFeatureDefn()
     517             : {
     518         660 :     const auto metadata = m_poArrowReader->parquet_reader()->metadata();
     519         660 :     const auto &kv_metadata = metadata->key_value_metadata();
     520             : 
     521         660 :     LoadGeoMetadata(kv_metadata);
     522             :     const auto oMapFieldNameToGDALSchemaFieldDefn =
     523         660 :         LoadGDALSchema(kv_metadata.get());
     524             : 
     525         660 :     LoadGDALMetadata(kv_metadata.get());
     526             : 
     527         660 :     if (!m_poArrowReader->GetSchema(&m_poSchema).ok())
     528             :     {
     529           0 :         return;
     530             :     }
     531             : 
     532         660 :     const bool bUseBBOX = CPLTestBool(CPLGetConfigOption(
     533        1320 :         ("OGR_" + GetDriverUCName() + "_USE_BBOX").c_str(), "YES"));
     534             : 
     535             :     // Keep track of declared bounding box columns in GeoParquet JSON metadata,
     536             :     // in order not to expose them as regular fields.
     537        1320 :     std::set<std::string> oSetBBOXColumns;
     538         660 :     if (bUseBBOX)
     539             :     {
     540        1303 :         for (const auto &iter : m_oMapGeometryColumns)
     541             :         {
     542        1296 :             std::string osBBOXColumn;
     543        1296 :             std::string osXMin, osYMin, osXMax, osYMax;
     544         648 :             if (ParseGeometryColumnCovering(iter.second, osBBOXColumn, osXMin,
     545             :                                             osYMin, osXMax, osYMax))
     546             :             {
     547         305 :                 oSetBBOXColumns.insert(osBBOXColumn);
     548             :             }
     549             :         }
     550             :     }
     551             : 
     552         660 :     const auto &fields = m_poSchema->fields();
     553         660 :     const auto poParquetSchema = metadata->schema();
     554             : 
     555             :     // Map from Parquet column name (with dot separator) to Parquet index
     556        1320 :     std::map<std::string, int> oMapParquetColumnNameToIdx;
     557         660 :     const int nParquetColumns = poParquetSchema->num_columns();
     558       21535 :     for (int iParquetCol = 0; iParquetCol < nParquetColumns; ++iParquetCol)
     559             :     {
     560       20875 :         const auto parquetColumn = poParquetSchema->Column(iParquetCol);
     561       20875 :         const auto parquetColumnName = parquetColumn->path()->ToDotString();
     562       20875 :         oMapParquetColumnNameToIdx[parquetColumnName] = iParquetCol;
     563             :     }
     564             : 
     565             :     // Synthetize a GeoParquet bounding box column definition when detecting
     566             :     // a Overture Map dataset < 2024-04-16-beta.0
     567         635 :     if ((m_oMapGeometryColumns.empty() ||
     568             :          // Below is for release 2024-01-17-alpha.0
     569        1295 :          (m_oMapGeometryColumns.find("geometry") !=
     570        1295 :               m_oMapGeometryColumns.end() &&
     571        1628 :           !m_oMapGeometryColumns["geometry"].GetObj("covering").IsValid() &&
     572        1215 :           m_oMapGeometryColumns["geometry"].GetString("encoding") == "WKB")) &&
     573         210 :         bUseBBOX &&
     574         870 :         oMapParquetColumnNameToIdx.find("geometry") !=
     575        1063 :             oMapParquetColumnNameToIdx.end() &&
     576         853 :         oMapParquetColumnNameToIdx.find("bbox.minx") !=
     577         854 :             oMapParquetColumnNameToIdx.end() &&
     578         661 :         oMapParquetColumnNameToIdx.find("bbox.miny") !=
     579         662 :             oMapParquetColumnNameToIdx.end() &&
     580         661 :         oMapParquetColumnNameToIdx.find("bbox.maxx") !=
     581        1981 :             oMapParquetColumnNameToIdx.end() &&
     582         661 :         oMapParquetColumnNameToIdx.find("bbox.maxy") !=
     583         661 :             oMapParquetColumnNameToIdx.end())
     584             :     {
     585           2 :         CPLJSONObject oDef;
     586           1 :         if (m_oMapGeometryColumns.find("geometry") !=
     587           2 :             m_oMapGeometryColumns.end())
     588             :         {
     589           0 :             oDef = m_oMapGeometryColumns["geometry"];
     590             :         }
     591           2 :         CPLJSONObject oCovering;
     592           1 :         oDef.Add("covering", oCovering);
     593           1 :         CPLJSONObject oBBOX;
     594           1 :         oCovering.Add("bbox", oBBOX);
     595             :         {
     596           1 :             CPLJSONArray oArray;
     597           1 :             oArray.Add("bbox");
     598           1 :             oArray.Add("minx");
     599           1 :             oBBOX.Add("xmin", oArray);
     600             :         }
     601             :         {
     602           1 :             CPLJSONArray oArray;
     603           1 :             oArray.Add("bbox");
     604           1 :             oArray.Add("miny");
     605           1 :             oBBOX.Add("ymin", oArray);
     606             :         }
     607             :         {
     608           1 :             CPLJSONArray oArray;
     609           1 :             oArray.Add("bbox");
     610           1 :             oArray.Add("maxx");
     611           1 :             oBBOX.Add("xmax", oArray);
     612             :         }
     613             :         {
     614           1 :             CPLJSONArray oArray;
     615           1 :             oArray.Add("bbox");
     616           1 :             oArray.Add("maxy");
     617           1 :             oBBOX.Add("ymax", oArray);
     618             :         }
     619           1 :         oSetBBOXColumns.insert("bbox");
     620           1 :         oDef.Add("encoding", "WKB");
     621           1 :         m_oMapGeometryColumns["geometry"] = std::move(oDef);
     622             :     }
     623             :     // Overture Maps 2024-04-16-beta.0 almost follows GeoParquet 1.1, except
     624             :     // they don't declare the "covering" element in the GeoParquet JSON metadata
     625        1318 :     else if (m_oMapGeometryColumns.find("geometry") !=
     626        1284 :                  m_oMapGeometryColumns.end() &&
     627        1242 :              bUseBBOX &&
     628        1622 :              !m_oMapGeometryColumns["geometry"].GetObj("covering").IsValid() &&
     629        1187 :              m_oMapGeometryColumns["geometry"].GetString("encoding") == "WKB" &&
     630         845 :              oMapParquetColumnNameToIdx.find("geometry") !=
     631        1031 :                  oMapParquetColumnNameToIdx.end() &&
     632         845 :              oMapParquetColumnNameToIdx.find("bbox.xmin") !=
     633         846 :                  oMapParquetColumnNameToIdx.end() &&
     634         660 :              oMapParquetColumnNameToIdx.find("bbox.ymin") !=
     635         661 :                  oMapParquetColumnNameToIdx.end() &&
     636         660 :              oMapParquetColumnNameToIdx.find("bbox.xmax") !=
     637        2603 :                  oMapParquetColumnNameToIdx.end() &&
     638         660 :              oMapParquetColumnNameToIdx.find("bbox.ymax") !=
     639         660 :                  oMapParquetColumnNameToIdx.end())
     640             :     {
     641           3 :         CPLJSONObject oDef = m_oMapGeometryColumns["geometry"];
     642           2 :         CPLJSONObject oCovering;
     643           1 :         oDef.Add("covering", oCovering);
     644           1 :         CPLJSONObject oBBOX;
     645           1 :         oCovering.Add("bbox", oBBOX);
     646             :         {
     647           1 :             CPLJSONArray oArray;
     648           1 :             oArray.Add("bbox");
     649           1 :             oArray.Add("xmin");
     650           1 :             oBBOX.Add("xmin", oArray);
     651             :         }
     652             :         {
     653           1 :             CPLJSONArray oArray;
     654           1 :             oArray.Add("bbox");
     655           1 :             oArray.Add("ymin");
     656           1 :             oBBOX.Add("ymin", oArray);
     657             :         }
     658             :         {
     659           1 :             CPLJSONArray oArray;
     660           1 :             oArray.Add("bbox");
     661           1 :             oArray.Add("xmax");
     662           1 :             oBBOX.Add("xmax", oArray);
     663             :         }
     664             :         {
     665           1 :             CPLJSONArray oArray;
     666           1 :             oArray.Add("bbox");
     667           1 :             oArray.Add("ymax");
     668           1 :             oBBOX.Add("ymax", oArray);
     669             :         }
     670           1 :         oSetBBOXColumns.insert("bbox");
     671           1 :         m_oMapGeometryColumns["geometry"] = std::move(oDef);
     672             :     }
     673             : 
     674         660 :     int iParquetCol = 0;
     675       15897 :     for (int i = 0; i < m_poSchema->num_fields(); ++i)
     676             :     {
     677       15237 :         const auto &field = fields[i];
     678             : 
     679             :         bool bParquetColValid =
     680       15237 :             CheckMatchArrowParquetColumnNames(iParquetCol, field);
     681       15237 :         if (!bParquetColValid)
     682           0 :             m_bHasMissingMappingToParquet = true;
     683             : 
     684       15271 :         if (!m_osFIDColumn.empty() && field->name() == m_osFIDColumn &&
     685          34 :             (field->type()->id() == arrow::Type::INT32 ||
     686          17 :              field->type()->id() == arrow::Type::INT64))
     687             :         {
     688          17 :             m_poFIDType = field->type();
     689          17 :             m_iFIDArrowColumn = i;
     690          17 :             if (bParquetColValid)
     691             :             {
     692          17 :                 m_iFIDParquetColumn = iParquetCol;
     693          17 :                 iParquetCol++;
     694             :             }
     695         324 :             continue;
     696             :         }
     697             : 
     698       15220 :         if (oSetBBOXColumns.find(field->name()) != oSetBBOXColumns.end())
     699             :         {
     700         307 :             m_oSetBBoxArrowColumns.insert(i);
     701         307 :             if (bParquetColValid)
     702         307 :                 iParquetCol++;
     703         307 :             continue;
     704             :         }
     705             : 
     706             :         const auto ComputeGeometryColumnTypeLambda =
     707         519 :             [this, bParquetColValid, iParquetCol, &poParquetSchema]()
     708             :         {
     709             :             // only with GeoParquet < 0.2.0
     710         346 :             if (bParquetColValid &&
     711         173 :                 poParquetSchema->Column(iParquetCol)->physical_type() ==
     712             :                     parquet::Type::BYTE_ARRAY)
     713             :             {
     714         173 :                 return ComputeGeometryColumnType(
     715         346 :                     m_poFeatureDefn->GetGeomFieldCount(), iParquetCol);
     716             :             }
     717           0 :             return wkbUnknown;
     718       14913 :         };
     719             : 
     720             :         const bool bGeometryField =
     721       14913 :             DealWithGeometryColumn(i, field, ComputeGeometryColumnTypeLambda);
     722       14913 :         if (bGeometryField)
     723             :         {
     724         660 :             const auto oIter = m_oMapGeometryColumns.find(field->name());
     725         660 :             if (bUseBBOX && oIter != m_oMapGeometryColumns.end())
     726             :             {
     727         648 :                 ProcessGeometryColumnCovering(field, oIter->second,
     728             :                                               oMapParquetColumnNameToIdx);
     729             :             }
     730             : 
     731        1948 :             if (bParquetColValid &&
     732        1288 :                 (field->type()->id() == arrow::Type::STRUCT ||
     733         628 :                  field->type()->id() == arrow::Type::LIST))
     734             :             {
     735             :                 // GeoArrow types
     736         296 :                 std::vector<int> anParquetCols;
     737        1800 :                 for (const auto &iterParquetCols : oMapParquetColumnNameToIdx)
     738             :                 {
     739        1504 :                     if (STARTS_WITH(
     740             :                             iterParquetCols.first.c_str(),
     741             :                             std::string(field->name()).append(".").c_str()))
     742             :                     {
     743         616 :                         iParquetCol =
     744         616 :                             std::max(iParquetCol, iterParquetCols.second);
     745         616 :                         anParquetCols.push_back(iterParquetCols.second);
     746             :                     }
     747             :                 }
     748         296 :                 m_anMapGeomFieldIndexToParquetColumns.push_back(anParquetCols);
     749         296 :                 ++iParquetCol;
     750             :             }
     751             :             else
     752             :             {
     753         364 :                 m_anMapGeomFieldIndexToParquetColumns.push_back(
     754         364 :                     {bParquetColValid ? iParquetCol : -1});
     755         364 :                 if (bParquetColValid)
     756         364 :                     iParquetCol++;
     757             :             }
     758             :         }
     759             :         else
     760             :         {
     761       14253 :             CreateFieldFromSchema(field, bParquetColValid, iParquetCol, {i},
     762             :                                   oMapFieldNameToGDALSchemaFieldDefn);
     763             :         }
     764             :     }
     765             : 
     766         660 :     CPLAssert(static_cast<int>(m_anMapFieldIndexToArrowColumn.size()) ==
     767             :               m_poFeatureDefn->GetFieldCount());
     768         660 :     CPLAssert(static_cast<int>(m_anMapFieldIndexToParquetColumn.size()) ==
     769             :               m_poFeatureDefn->GetFieldCount());
     770         660 :     CPLAssert(static_cast<int>(m_anMapGeomFieldIndexToArrowColumn.size()) ==
     771             :               m_poFeatureDefn->GetGeomFieldCount());
     772         660 :     CPLAssert(static_cast<int>(m_anMapGeomFieldIndexToParquetColumns.size()) ==
     773             :               m_poFeatureDefn->GetGeomFieldCount());
     774             : 
     775         660 :     if (!fields.empty())
     776             :     {
     777             :         try
     778             :         {
     779        1266 :             auto poRowGroup = m_poArrowReader->parquet_reader()->RowGroup(0);
     780         607 :             if (poRowGroup)
     781             :             {
     782        1214 :                 auto poColumn = poRowGroup->metadata()->ColumnChunk(0);
     783         607 :                 CPLDebug("PARQUET", "Compression (of first column): %s",
     784             :                          arrow::util::Codec::GetCodecAsString(
     785         607 :                              poColumn->compression())
     786             :                              .c_str());
     787             :             }
     788             :         }
     789          52 :         catch (const std::exception &)
     790             :         {
     791             :         }
     792             :     }
     793             : }
     794             : 
     795             : /************************************************************************/
     796             : /*                  ProcessGeometryColumnCovering()                     */
     797             : /************************************************************************/
     798             : 
     799             : /** Process GeoParquet JSON geometry field object to extract information about
     800             :  * its bounding box column, and appropriately fill m_oMapGeomFieldIndexToGeomColBBOX
     801             :  * and m_oMapGeomFieldIndexToGeomColBBOXParquet members with information on that
     802             :  * bounding box column.
     803             :  */
     804         648 : void OGRParquetLayer::ProcessGeometryColumnCovering(
     805             :     const std::shared_ptr<arrow::Field> &field,
     806             :     const CPLJSONObject &oJSONGeometryColumn,
     807             :     const std::map<std::string, int> &oMapParquetColumnNameToIdx)
     808             : {
     809        1296 :     std::string osBBOXColumn;
     810        1296 :     std::string osXMin, osYMin, osXMax, osYMax;
     811         648 :     if (ParseGeometryColumnCovering(oJSONGeometryColumn, osBBOXColumn, osXMin,
     812             :                                     osYMin, osXMax, osYMax))
     813             :     {
     814         307 :         OGRArrowLayer::GeomColBBOX sDesc;
     815         307 :         sDesc.iArrowCol = m_poSchema->GetFieldIndex(osBBOXColumn);
     816         614 :         const auto fieldBBOX = m_poSchema->GetFieldByName(osBBOXColumn);
     817         614 :         if (sDesc.iArrowCol >= 0 && fieldBBOX &&
     818         307 :             fieldBBOX->type()->id() == arrow::Type::STRUCT)
     819             :         {
     820             :             const auto fieldBBOXStruct =
     821         614 :                 std::static_pointer_cast<arrow::StructType>(fieldBBOX->type());
     822         614 :             const auto fieldXMin = fieldBBOXStruct->GetFieldByName(osXMin);
     823         614 :             const auto fieldYMin = fieldBBOXStruct->GetFieldByName(osYMin);
     824         614 :             const auto fieldXMax = fieldBBOXStruct->GetFieldByName(osXMax);
     825         614 :             const auto fieldYMax = fieldBBOXStruct->GetFieldByName(osYMax);
     826         307 :             const int nXMinIdx = fieldBBOXStruct->GetFieldIndex(osXMin);
     827         307 :             const int nYMinIdx = fieldBBOXStruct->GetFieldIndex(osYMin);
     828         307 :             const int nXMaxIdx = fieldBBOXStruct->GetFieldIndex(osXMax);
     829         307 :             const int nYMaxIdx = fieldBBOXStruct->GetFieldIndex(osYMax);
     830             :             const auto oIterParquetIdxXMin = oMapParquetColumnNameToIdx.find(
     831         307 :                 std::string(osBBOXColumn).append(".").append(osXMin));
     832             :             const auto oIterParquetIdxYMin = oMapParquetColumnNameToIdx.find(
     833         307 :                 std::string(osBBOXColumn).append(".").append(osYMin));
     834             :             const auto oIterParquetIdxXMax = oMapParquetColumnNameToIdx.find(
     835         307 :                 std::string(osBBOXColumn).append(".").append(osXMax));
     836             :             const auto oIterParquetIdxYMax = oMapParquetColumnNameToIdx.find(
     837         307 :                 std::string(osBBOXColumn).append(".").append(osYMax));
     838         307 :             if (nXMinIdx >= 0 && nYMinIdx >= 0 && nXMaxIdx >= 0 &&
     839         614 :                 nYMaxIdx >= 0 && fieldXMin && fieldYMin && fieldXMax &&
     840         614 :                 fieldYMax &&
     841         614 :                 oIterParquetIdxXMin != oMapParquetColumnNameToIdx.end() &&
     842         614 :                 oIterParquetIdxYMin != oMapParquetColumnNameToIdx.end() &&
     843         614 :                 oIterParquetIdxXMax != oMapParquetColumnNameToIdx.end() &&
     844         614 :                 oIterParquetIdxYMax != oMapParquetColumnNameToIdx.end() &&
     845         308 :                 (fieldXMin->type()->id() == arrow::Type::FLOAT ||
     846           1 :                  fieldXMin->type()->id() == arrow::Type::DOUBLE) &&
     847         307 :                 fieldXMin->type()->id() == fieldYMin->type()->id() &&
     848         921 :                 fieldXMin->type()->id() == fieldXMax->type()->id() &&
     849         307 :                 fieldXMin->type()->id() == fieldYMax->type()->id())
     850             :             {
     851         307 :                 CPLDebug("PARQUET",
     852             :                          "Bounding box column '%s' detected for "
     853             :                          "geometry column '%s'",
     854         307 :                          osBBOXColumn.c_str(), field->name().c_str());
     855         307 :                 sDesc.iArrowSubfieldXMin = nXMinIdx;
     856         307 :                 sDesc.iArrowSubfieldYMin = nYMinIdx;
     857         307 :                 sDesc.iArrowSubfieldXMax = nXMaxIdx;
     858         307 :                 sDesc.iArrowSubfieldYMax = nYMaxIdx;
     859         307 :                 sDesc.bIsFloat =
     860         307 :                     (fieldXMin->type()->id() == arrow::Type::FLOAT);
     861             : 
     862             :                 m_oMapGeomFieldIndexToGeomColBBOX
     863         307 :                     [m_poFeatureDefn->GetGeomFieldCount() - 1] =
     864         307 :                         std::move(sDesc);
     865             : 
     866         307 :                 GeomColBBOXParquet sDescParquet;
     867         307 :                 sDescParquet.iParquetXMin = oIterParquetIdxXMin->second;
     868         307 :                 sDescParquet.iParquetYMin = oIterParquetIdxYMin->second;
     869         307 :                 sDescParquet.iParquetXMax = oIterParquetIdxXMax->second;
     870         307 :                 sDescParquet.iParquetYMax = oIterParquetIdxYMax->second;
     871        3238 :                 for (const auto &iterParquetCols : oMapParquetColumnNameToIdx)
     872             :                 {
     873        2931 :                     if (STARTS_WITH(
     874             :                             iterParquetCols.first.c_str(),
     875             :                             std::string(osBBOXColumn).append(".").c_str()))
     876             :                     {
     877        1228 :                         sDescParquet.anParquetCols.push_back(
     878        1228 :                             iterParquetCols.second);
     879             :                     }
     880             :                 }
     881             :                 m_oMapGeomFieldIndexToGeomColBBOXParquet
     882         614 :                     [m_poFeatureDefn->GetGeomFieldCount() - 1] =
     883         614 :                         std::move(sDescParquet);
     884             :             }
     885             :         }
     886             :     }
     887         648 : }
     888             : 
     889             : /************************************************************************/
     890             : /*                CheckMatchArrowParquetColumnNames()                   */
     891             : /************************************************************************/
     892             : 
     893       16535 : bool OGRParquetLayer::CheckMatchArrowParquetColumnNames(
     894             :     int &iParquetCol, const std::shared_ptr<arrow::Field> &field) const
     895             : {
     896       33070 :     const auto metadata = m_poArrowReader->parquet_reader()->metadata();
     897       16535 :     const auto poParquetSchema = metadata->schema();
     898       16535 :     const int nParquetColumns = poParquetSchema->num_columns();
     899       16535 :     const auto &fieldName = field->name();
     900       16535 :     const int iParquetColBefore = iParquetCol;
     901             : 
     902       16971 :     while (iParquetCol < nParquetColumns)
     903             :     {
     904       16971 :         const auto parquetColumn = poParquetSchema->Column(iParquetCol);
     905       16971 :         const auto parquetColumnName = parquetColumn->path()->ToDotString();
     906       36053 :         if (fieldName == parquetColumnName ||
     907        9541 :             (parquetColumnName.size() > fieldName.size() &&
     908        9541 :              STARTS_WITH(parquetColumnName.c_str(), fieldName.c_str()) &&
     909        9105 :              parquetColumnName[fieldName.size()] == '.'))
     910             :         {
     911       16535 :             return true;
     912             :         }
     913             :         else
     914             :         {
     915         436 :             iParquetCol++;
     916             :         }
     917             :     }
     918             : 
     919           0 :     CPLError(CE_Warning, CPLE_AppDefined,
     920             :              "Cannot match Arrow column name %s with a Parquet one",
     921             :              fieldName.c_str());
     922           0 :     iParquetCol = iParquetColBefore;
     923           0 :     return false;
     924             : }
     925             : 
     926             : /************************************************************************/
     927             : /*                         CreateFieldFromSchema()                      */
     928             : /************************************************************************/
     929             : 
     930       15551 : void OGRParquetLayer::CreateFieldFromSchema(
     931             :     const std::shared_ptr<arrow::Field> &field, bool bParquetColValid,
     932             :     int &iParquetCol, const std::vector<int> &path,
     933             :     const std::map<std::string, std::unique_ptr<OGRFieldDefn>>
     934             :         &oMapFieldNameToGDALSchemaFieldDefn)
     935             : {
     936       15551 :     OGRFieldDefn oField(field->name().c_str(), OFTString);
     937       15551 :     OGRFieldType eType = OFTString;
     938       15551 :     OGRFieldSubType eSubType = OFSTNone;
     939       15551 :     bool bTypeOK = true;
     940             : 
     941       15551 :     auto type = field->type();
     942       15551 :     if (type->id() == arrow::Type::DICTIONARY && path.size() == 1)
     943             :     {
     944             :         const auto dictionaryType =
     945         346 :             std::static_pointer_cast<arrow::DictionaryType>(field->type());
     946         346 :         const auto indexType = dictionaryType->index_type();
     947         346 :         if (dictionaryType->value_type()->id() == arrow::Type::STRING &&
     948         173 :             IsIntegerArrowType(indexType->id()))
     949             :         {
     950         173 :             if (bParquetColValid)
     951             :             {
     952         346 :                 std::string osDomainName(field->name() + "Domain");
     953         173 :                 m_poDS->RegisterDomainName(osDomainName,
     954         173 :                                            m_poFeatureDefn->GetFieldCount());
     955         173 :                 oField.SetDomainName(osDomainName);
     956             :             }
     957         173 :             type = indexType;
     958             :         }
     959             :         else
     960             :         {
     961           0 :             bTypeOK = false;
     962             :         }
     963             :     }
     964             : 
     965       15551 :     int nParquetColIncrement = 1;
     966       15551 :     switch (type->id())
     967             :     {
     968         369 :         case arrow::Type::STRUCT:
     969             :         {
     970         738 :             const auto subfields = field->Flatten();
     971         738 :             auto newpath = path;
     972         369 :             newpath.push_back(0);
     973        1667 :             for (int j = 0; j < static_cast<int>(subfields.size()); j++)
     974             :             {
     975        1298 :                 const auto &subfield = subfields[j];
     976             :                 bParquetColValid =
     977        1298 :                     CheckMatchArrowParquetColumnNames(iParquetCol, subfield);
     978        1298 :                 if (!bParquetColValid)
     979           0 :                     m_bHasMissingMappingToParquet = true;
     980        1298 :                 newpath.back() = j;
     981        1298 :                 CreateFieldFromSchema(subfield, bParquetColValid, iParquetCol,
     982             :                                       newpath,
     983             :                                       oMapFieldNameToGDALSchemaFieldDefn);
     984             :             }
     985         369 :             return;  // return intended, not break
     986             :         }
     987             : 
     988        3085 :         case arrow::Type::MAP:
     989             :         {
     990             :             // A arrow map maps to 2 Parquet columns
     991        3085 :             nParquetColIncrement = 2;
     992        3085 :             break;
     993             :         }
     994             : 
     995       12097 :         default:
     996       12097 :             break;
     997             :     }
     998             : 
     999       15182 :     if (bTypeOK)
    1000             :     {
    1001       15182 :         bTypeOK = MapArrowTypeToOGR(type, field, oField, eType, eSubType, path,
    1002             :                                     oMapFieldNameToGDALSchemaFieldDefn);
    1003       15182 :         if (bTypeOK)
    1004             :         {
    1005       15016 :             m_apoArrowDataTypes.push_back(type);
    1006       30032 :             m_anMapFieldIndexToParquetColumn.push_back(
    1007       15016 :                 bParquetColValid ? iParquetCol : -1);
    1008             :         }
    1009             :     }
    1010             : 
    1011       15182 :     if (bParquetColValid)
    1012       15182 :         iParquetCol += nParquetColIncrement;
    1013             : }
    1014             : 
    1015             : /************************************************************************/
    1016             : /*                          BuildDomain()                               */
    1017             : /************************************************************************/
    1018             : 
    1019             : std::unique_ptr<OGRFieldDomain>
    1020          13 : OGRParquetLayer::BuildDomain(const std::string &osDomainName,
    1021             :                              int iFieldIndex) const
    1022             : {
    1023             : #ifdef DEBUG
    1024          13 :     const int iArrowCol = m_anMapFieldIndexToArrowColumn[iFieldIndex][0];
    1025             :     (void)iArrowCol;
    1026          13 :     CPLAssert(m_poSchema->fields()[iArrowCol]->type()->id() ==
    1027             :               arrow::Type::DICTIONARY);
    1028             : #endif
    1029          13 :     const int iParquetCol = m_anMapFieldIndexToParquetColumn[iFieldIndex];
    1030          13 :     CPLAssert(iParquetCol >= 0);
    1031          13 :     std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1032          13 :     const auto oldBatchSize = m_poArrowReader->properties().batch_size();
    1033          13 :     m_poArrowReader->set_batch_size(1);
    1034          13 :     CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
    1035             :         {0}, {iParquetCol}, &poRecordBatchReader));
    1036          13 :     if (poRecordBatchReader != nullptr)
    1037             :     {
    1038           0 :         std::shared_ptr<arrow::RecordBatch> poBatch;
    1039          13 :         auto status = poRecordBatchReader->ReadNext(&poBatch);
    1040          13 :         if (!status.ok())
    1041             :         {
    1042           0 :             CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
    1043           0 :                      status.message().c_str());
    1044             :         }
    1045          13 :         else if (poBatch)
    1046             :         {
    1047          13 :             m_poArrowReader->set_batch_size(oldBatchSize);
    1048          13 :             return BuildDomainFromBatch(osDomainName, poBatch, 0);
    1049             :         }
    1050             :     }
    1051           0 :     m_poArrowReader->set_batch_size(oldBatchSize);
    1052           0 :     return nullptr;
    1053             : }
    1054             : 
    1055             : /************************************************************************/
    1056             : /*                     ComputeGeometryColumnType()                      */
    1057             : /************************************************************************/
    1058             : 
    1059             : OGRwkbGeometryType
    1060         173 : OGRParquetLayer::ComputeGeometryColumnType(int iGeomCol, int iParquetCol) const
    1061             : {
    1062             :     // Compute type of geometry column by iterating over each geometry, and
    1063             :     // looking at the WKB geometry type in the first 5 bytes of each geometry.
    1064             : 
    1065         173 :     OGRwkbGeometryType eGeomType = wkbNone;
    1066         173 :     std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1067             : 
    1068         173 :     std::vector<int> anRowGroups;
    1069         173 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    1070         173 :     anRowGroups.reserve(nNumGroups);
    1071         512 :     for (int i = 0; i < nNumGroups; ++i)
    1072         339 :         anRowGroups.push_back(i);
    1073         173 :     CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
    1074             :         anRowGroups, {iParquetCol}, &poRecordBatchReader));
    1075         173 :     if (poRecordBatchReader != nullptr)
    1076             :     {
    1077         346 :         std::shared_ptr<arrow::RecordBatch> poBatch;
    1078             :         while (true)
    1079             :         {
    1080         348 :             auto status = poRecordBatchReader->ReadNext(&poBatch);
    1081         348 :             if (!status.ok())
    1082             :             {
    1083           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
    1084           0 :                          status.message().c_str());
    1085           0 :                 break;
    1086             :             }
    1087         348 :             else if (!poBatch)
    1088         171 :                 break;
    1089             : 
    1090         177 :             eGeomType = ComputeGeometryColumnTypeProcessBatch(poBatch, iGeomCol,
    1091             :                                                               0, eGeomType);
    1092         177 :             if (eGeomType == wkbUnknown)
    1093           2 :                 break;
    1094         175 :         }
    1095             :     }
    1096             : 
    1097         346 :     return eGeomType == wkbNone ? wkbUnknown : eGeomType;
    1098             : }
    1099             : 
    1100             : /************************************************************************/
    1101             : /*                       GetFeatureExplicitFID()                        */
    1102             : /************************************************************************/
    1103             : 
    1104           4 : OGRFeature *OGRParquetLayer::GetFeatureExplicitFID(GIntBig nFID)
    1105             : {
    1106           4 :     std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1107             : 
    1108           8 :     std::vector<int> anRowGroups;
    1109           4 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    1110           4 :     anRowGroups.reserve(nNumGroups);
    1111          16 :     for (int i = 0; i < nNumGroups; ++i)
    1112          12 :         anRowGroups.push_back(i);
    1113           4 :     if (m_bIgnoredFields)
    1114             :     {
    1115           4 :         CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
    1116           2 :             anRowGroups, m_anRequestedParquetColumns, &poRecordBatchReader));
    1117             :     }
    1118             :     else
    1119             :     {
    1120           2 :         CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
    1121             :             anRowGroups, &poRecordBatchReader));
    1122             :     }
    1123           4 :     if (poRecordBatchReader != nullptr)
    1124             :     {
    1125           4 :         std::shared_ptr<arrow::RecordBatch> poBatch;
    1126             :         while (true)
    1127             :         {
    1128          14 :             auto status = poRecordBatchReader->ReadNext(&poBatch);
    1129          14 :             if (!status.ok())
    1130             :             {
    1131           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
    1132           0 :                          status.message().c_str());
    1133           0 :                 break;
    1134             :             }
    1135          14 :             else if (!poBatch)
    1136           2 :                 break;
    1137             : 
    1138          12 :             const auto array = poBatch->column(
    1139          12 :                 m_bIgnoredFields ? m_nRequestedFIDColumn : m_iFIDArrowColumn);
    1140          12 :             const auto arrayPtr = array.get();
    1141          12 :             const auto arrayTypeId = array->type_id();
    1142          30 :             for (int64_t nIdxInBatch = 0; nIdxInBatch < poBatch->num_rows();
    1143             :                  nIdxInBatch++)
    1144             :             {
    1145          20 :                 if (!array->IsNull(nIdxInBatch))
    1146             :                 {
    1147          20 :                     if (arrayTypeId == arrow::Type::INT64)
    1148             :                     {
    1149          20 :                         const auto castArray =
    1150             :                             static_cast<const arrow::Int64Array *>(arrayPtr);
    1151          20 :                         if (castArray->Value(nIdxInBatch) == nFID)
    1152             :                         {
    1153           2 :                             return ReadFeature(nIdxInBatch, poBatch->columns());
    1154             :                         }
    1155             :                     }
    1156           0 :                     else if (arrayTypeId == arrow::Type::INT32)
    1157             :                     {
    1158           0 :                         const auto castArray =
    1159             :                             static_cast<const arrow::Int32Array *>(arrayPtr);
    1160           0 :                         if (castArray->Value(nIdxInBatch) == nFID)
    1161             :                         {
    1162           0 :                             return ReadFeature(nIdxInBatch, poBatch->columns());
    1163             :                         }
    1164             :                     }
    1165             :                 }
    1166             :             }
    1167          10 :         }
    1168             :     }
    1169           2 :     return nullptr;
    1170             : }
    1171             : 
    1172             : /************************************************************************/
    1173             : /*                         GetFeatureByIndex()                          */
    1174             : /************************************************************************/
    1175             : 
    1176          23 : OGRFeature *OGRParquetLayer::GetFeatureByIndex(GIntBig nFID)
    1177             : {
    1178             : 
    1179          23 :     if (nFID < 0)
    1180           3 :         return nullptr;
    1181             : 
    1182          40 :     const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    1183          20 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    1184          20 :     int64_t nAccRows = 0;
    1185          29 :     for (int iGroup = 0; iGroup < nNumGroups; ++iGroup)
    1186             :     {
    1187             :         const int64_t nNextAccRows =
    1188          23 :             nAccRows + metadata->RowGroup(iGroup)->num_rows();
    1189          23 :         if (nFID < nNextAccRows)
    1190             :         {
    1191          14 :             std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1192          14 :             arrow::Status status;
    1193          14 :             if (m_bIgnoredFields)
    1194             :             {
    1195           0 :                 status = m_poArrowReader->GetRecordBatchReader(
    1196           0 :                     {iGroup}, m_anRequestedParquetColumns,
    1197           0 :                     &poRecordBatchReader);
    1198             :             }
    1199             :             else
    1200             :             {
    1201          28 :                 status = m_poArrowReader->GetRecordBatchReader(
    1202          14 :                     {iGroup}, &poRecordBatchReader);
    1203             :             }
    1204          14 :             if (poRecordBatchReader == nullptr)
    1205             :             {
    1206           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1207             :                          "GetRecordBatchReader() failed: %s",
    1208           0 :                          status.message().c_str());
    1209           0 :                 return nullptr;
    1210             :             }
    1211             : 
    1212          14 :             const int64_t nExpectedIdxInGroup = nFID - nAccRows;
    1213          14 :             int64_t nIdxInGroup = 0;
    1214             :             while (true)
    1215             :             {
    1216           0 :                 std::shared_ptr<arrow::RecordBatch> poBatch;
    1217          14 :                 status = poRecordBatchReader->ReadNext(&poBatch);
    1218          14 :                 if (!status.ok())
    1219             :                 {
    1220           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1221           0 :                              "ReadNext() failed: %s", status.message().c_str());
    1222           0 :                     return nullptr;
    1223             :                 }
    1224          14 :                 if (poBatch == nullptr)
    1225             :                 {
    1226           0 :                     return nullptr;
    1227             :                 }
    1228          14 :                 if (nExpectedIdxInGroup < nIdxInGroup + poBatch->num_rows())
    1229             :                 {
    1230          14 :                     const auto nIdxInBatch = nExpectedIdxInGroup - nIdxInGroup;
    1231             :                     auto poFeature =
    1232          14 :                         ReadFeature(nIdxInBatch, poBatch->columns());
    1233          14 :                     poFeature->SetFID(nFID);
    1234          14 :                     return poFeature;
    1235             :                 }
    1236           0 :                 nIdxInGroup += poBatch->num_rows();
    1237           0 :             }
    1238             :         }
    1239           9 :         nAccRows = nNextAccRows;
    1240             :     }
    1241           6 :     return nullptr;
    1242             : }
    1243             : 
    1244             : /************************************************************************/
    1245             : /*                           GetFeature()                               */
    1246             : /************************************************************************/
    1247             : 
    1248          27 : OGRFeature *OGRParquetLayer::GetFeature(GIntBig nFID)
    1249             : {
    1250          27 :     if (!m_osFIDColumn.empty())
    1251             :     {
    1252           4 :         return GetFeatureExplicitFID(nFID);
    1253             :     }
    1254             :     else
    1255             :     {
    1256          23 :         return GetFeatureByIndex(nFID);
    1257             :     }
    1258             : }
    1259             : 
    1260             : /************************************************************************/
    1261             : /*                           ResetReading()                             */
    1262             : /************************************************************************/
    1263             : 
    1264        3163 : void OGRParquetLayer::ResetReading()
    1265             : {
    1266        3163 :     OGRParquetLayerBase::ResetReading();
    1267        3163 :     m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
    1268        3163 :     m_nFeatureIdxSelected = 0;
    1269        3163 :     if (!m_asFeatureIdxRemapping.empty())
    1270             :     {
    1271        1620 :         m_nFeatureIdx = m_oFeatureIdxRemappingIter->second;
    1272        1620 :         ++m_oFeatureIdxRemappingIter;
    1273             :     }
    1274        3163 : }
    1275             : 
    1276             : /************************************************************************/
    1277             : /*                      CreateRecordBatchReader()                       */
    1278             : /************************************************************************/
    1279             : 
    1280         476 : bool OGRParquetLayer::CreateRecordBatchReader(int iStartingRowGroup)
    1281             : {
    1282         952 :     std::vector<int> anRowGroups;
    1283         476 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    1284         476 :     anRowGroups.reserve(nNumGroups - iStartingRowGroup);
    1285        1185 :     for (int i = iStartingRowGroup; i < nNumGroups; ++i)
    1286         709 :         anRowGroups.push_back(i);
    1287         952 :     return CreateRecordBatchReader(anRowGroups);
    1288             : }
    1289             : 
    1290         718 : bool OGRParquetLayer::CreateRecordBatchReader(
    1291             :     const std::vector<int> &anRowGroups)
    1292             : {
    1293         718 :     arrow::Status status;
    1294         718 :     if (m_bIgnoredFields)
    1295             :     {
    1296         410 :         status = m_poArrowReader->GetRecordBatchReader(
    1297         205 :             anRowGroups, m_anRequestedParquetColumns, &m_poRecordBatchReader);
    1298             :     }
    1299             :     else
    1300             :     {
    1301        1026 :         status = m_poArrowReader->GetRecordBatchReader(anRowGroups,
    1302         513 :                                                        &m_poRecordBatchReader);
    1303             :     }
    1304         718 :     if (m_poRecordBatchReader == nullptr)
    1305             :     {
    1306           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1307           0 :                  "GetRecordBatchReader() failed: %s", status.message().c_str());
    1308           0 :         return false;
    1309             :     }
    1310         718 :     return true;
    1311             : }
    1312             : 
    1313             : /************************************************************************/
    1314             : /*                       IsConstraintPossible()                         */
    1315             : /************************************************************************/
    1316             : 
    1317             : enum class IsConstraintPossibleRes
    1318             : {
    1319             :     YES,
    1320             :     NO,
    1321             :     UNKNOWN
    1322             : };
    1323             : 
    1324             : template <class T>
    1325         208 : static IsConstraintPossibleRes IsConstraintPossible(int nOperation, T v, T min,
    1326             :                                                     T max)
    1327             : {
    1328         208 :     if (nOperation == SWQ_EQ)
    1329             :     {
    1330         132 :         if (v < min || v > max)
    1331             :         {
    1332          53 :             return IsConstraintPossibleRes::NO;
    1333             :         }
    1334             :     }
    1335          76 :     else if (nOperation == SWQ_NE)
    1336             :     {
    1337          36 :         if (v == min && v == max)
    1338             :         {
    1339           0 :             return IsConstraintPossibleRes::NO;
    1340             :         }
    1341             :     }
    1342          40 :     else if (nOperation == SWQ_LE)
    1343             :     {
    1344          10 :         if (v < min)
    1345             :         {
    1346           4 :             return IsConstraintPossibleRes::NO;
    1347             :         }
    1348             :     }
    1349          30 :     else if (nOperation == SWQ_LT)
    1350             :     {
    1351          10 :         if (v <= min)
    1352             :         {
    1353           4 :             return IsConstraintPossibleRes::NO;
    1354             :         }
    1355             :     }
    1356          20 :     else if (nOperation == SWQ_GE)
    1357             :     {
    1358          10 :         if (v > max)
    1359             :         {
    1360           4 :             return IsConstraintPossibleRes::NO;
    1361             :         }
    1362             :     }
    1363          10 :     else if (nOperation == SWQ_GT)
    1364             :     {
    1365          10 :         if (v >= max)
    1366             :         {
    1367           6 :             return IsConstraintPossibleRes::NO;
    1368             :         }
    1369             :     }
    1370             :     else
    1371             :     {
    1372           0 :         CPLDebug("PARQUET",
    1373             :                  "IsConstraintPossible: Unhandled operation type: %d",
    1374             :                  nOperation);
    1375           0 :         return IsConstraintPossibleRes::UNKNOWN;
    1376             :     }
    1377         137 :     return IsConstraintPossibleRes::YES;
    1378             : }
    1379             : 
    1380             : /************************************************************************/
    1381             : /*                           IncrFeatureIdx()                           */
    1382             : /************************************************************************/
    1383             : 
    1384        6593 : void OGRParquetLayer::IncrFeatureIdx()
    1385             : {
    1386        6593 :     ++m_nFeatureIdxSelected;
    1387        6593 :     ++m_nFeatureIdx;
    1388        7509 :     if (m_iFIDArrowColumn < 0 && !m_asFeatureIdxRemapping.empty() &&
    1389        7509 :         m_oFeatureIdxRemappingIter != m_asFeatureIdxRemapping.end())
    1390             :     {
    1391         125 :         if (m_nFeatureIdxSelected == m_oFeatureIdxRemappingIter->first)
    1392             :         {
    1393          43 :             m_nFeatureIdx = m_oFeatureIdxRemappingIter->second;
    1394          43 :             ++m_oFeatureIdxRemappingIter;
    1395             :         }
    1396             :     }
    1397        6593 : }
    1398             : 
    1399             : /************************************************************************/
    1400             : /*                           ReadNextBatch()                            */
    1401             : /************************************************************************/
    1402             : 
    1403        1429 : bool OGRParquetLayer::ReadNextBatch()
    1404             : {
    1405        1429 :     m_nIdxInBatch = 0;
    1406             : 
    1407        1429 :     if (m_bSingleBatch)
    1408             :     {
    1409          17 :         CPLAssert(m_iRecordBatch == 0);
    1410          17 :         CPLAssert(m_poBatch != nullptr);
    1411          17 :         return false;
    1412             :     }
    1413             : 
    1414        1412 :     CPLAssert((m_iRecordBatch == -1 && m_poRecordBatchReader == nullptr) ||
    1415             :               (m_iRecordBatch >= 0 && m_poRecordBatchReader != nullptr));
    1416             : 
    1417        1412 :     if (m_poRecordBatchReader == nullptr)
    1418             :     {
    1419         722 :         m_asFeatureIdxRemapping.clear();
    1420             : 
    1421         722 :         bool bIterateEverything = false;
    1422         722 :         std::vector<int> anSelectedGroups;
    1423             :         const auto oIterToGeomColBBOX =
    1424         722 :             m_oMapGeomFieldIndexToGeomColBBOXParquet.find(m_iGeomFieldFilter);
    1425             :         const bool bUSEBBOXFields =
    1426         188 :             (m_poFilterGeom &&
    1427         188 :              oIterToGeomColBBOX !=
    1428         910 :                  m_oMapGeomFieldIndexToGeomColBBOXParquet.end() &&
    1429          89 :              CPLTestBool(CPLGetConfigOption(
    1430         811 :                  ("OGR_" + GetDriverUCName() + "_USE_BBOX").c_str(), "YES")));
    1431             :         const bool bIsGeoArrowStruct =
    1432        1444 :             (m_iGeomFieldFilter >= 0 &&
    1433         722 :              m_iGeomFieldFilter < static_cast<int>(m_aeGeomEncoding.size()) &&
    1434         715 :              m_iGeomFieldFilter <
    1435             :                  static_cast<int>(
    1436        1430 :                      m_anMapGeomFieldIndexToParquetColumns.size()) &&
    1437         715 :              m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter].size() >=
    1438        1444 :                  2 &&
    1439         204 :              (m_aeGeomEncoding[m_iGeomFieldFilter] ==
    1440         172 :                   OGRArrowGeomEncoding::GEOARROW_STRUCT_POINT ||
    1441         172 :               m_aeGeomEncoding[m_iGeomFieldFilter] ==
    1442         140 :                   OGRArrowGeomEncoding::GEOARROW_STRUCT_LINESTRING ||
    1443         140 :               m_aeGeomEncoding[m_iGeomFieldFilter] ==
    1444          96 :                   OGRArrowGeomEncoding::GEOARROW_STRUCT_POLYGON ||
    1445          96 :               m_aeGeomEncoding[m_iGeomFieldFilter] ==
    1446          72 :                   OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOINT ||
    1447          72 :               m_aeGeomEncoding[m_iGeomFieldFilter] ==
    1448          40 :                   OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTILINESTRING ||
    1449          40 :               m_aeGeomEncoding[m_iGeomFieldFilter] ==
    1450         722 :                   OGRArrowGeomEncoding::GEOARROW_STRUCT_MULTIPOLYGON));
    1451             : 
    1452        1232 :         if (m_asAttributeFilterConstraints.empty() && !bUSEBBOXFields &&
    1453         510 :             !(bIsGeoArrowStruct && m_poFilterGeom))
    1454             :         {
    1455         464 :             bIterateEverything = true;
    1456             :         }
    1457             :         else
    1458             :         {
    1459         258 :             const int nNumGroups = m_poArrowReader->num_row_groups();
    1460             :             OGRField sMin;
    1461             :             OGRField sMax;
    1462         258 :             OGR_RawField_SetNull(&sMin);
    1463         258 :             OGR_RawField_SetNull(&sMax);
    1464         258 :             bool bFoundMin = false;
    1465         258 :             bool bFoundMax = false;
    1466         258 :             OGRFieldType eType = OFTMaxType;
    1467         258 :             OGRFieldSubType eSubType = OFSTNone;
    1468         516 :             std::string osMinTmp, osMaxTmp;
    1469         258 :             int64_t nFeatureIdxSelected = 0;
    1470         258 :             int64_t nFeatureIdxTotal = 0;
    1471             : 
    1472         258 :             int iXMinField = -1;
    1473         258 :             int iYMinField = -1;
    1474         258 :             int iXMaxField = -1;
    1475         258 :             int iYMaxField = -1;
    1476             : 
    1477         258 :             if (bIsGeoArrowStruct)
    1478             :             {
    1479             :                 const auto metadata =
    1480         184 :                     m_poArrowReader->parquet_reader()->metadata();
    1481          92 :                 const auto poParquetSchema = metadata->schema();
    1482         228 :                 for (int iParquetCol :
    1483         548 :                      m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter])
    1484             :                 {
    1485             :                     const auto parquetColumn =
    1486         228 :                         poParquetSchema->Column(iParquetCol);
    1487             :                     const auto parquetColumnName =
    1488         456 :                         parquetColumn->path()->ToDotString();
    1489         456 :                     if (parquetColumnName.size() > 2 &&
    1490         228 :                         parquetColumnName.find(".x") ==
    1491         228 :                             parquetColumnName.size() - 2)
    1492             :                     {
    1493          92 :                         iXMinField = iParquetCol;
    1494          92 :                         iXMaxField = iParquetCol;
    1495             :                     }
    1496         272 :                     else if (parquetColumnName.size() > 2 &&
    1497         136 :                              parquetColumnName.find(".y") ==
    1498         136 :                                  parquetColumnName.size() - 2)
    1499             :                     {
    1500          92 :                         iYMinField = iParquetCol;
    1501          92 :                         iYMaxField = iParquetCol;
    1502             :                     }
    1503             :                 }
    1504             :             }
    1505         166 :             else if (bUSEBBOXFields)
    1506             :             {
    1507          43 :                 iXMinField = oIterToGeomColBBOX->second.iParquetXMin;
    1508          43 :                 iYMinField = oIterToGeomColBBOX->second.iParquetYMin;
    1509          43 :                 iXMaxField = oIterToGeomColBBOX->second.iParquetXMax;
    1510          43 :                 iYMaxField = oIterToGeomColBBOX->second.iParquetYMax;
    1511             :             }
    1512             : 
    1513         632 :             for (int iRowGroup = 0;
    1514         632 :                  iRowGroup < nNumGroups && !bIterateEverything; ++iRowGroup)
    1515             :             {
    1516         374 :                 bool bSelectGroup = true;
    1517             :                 auto poRowGroup =
    1518         374 :                     GetReader()->parquet_reader()->RowGroup(iRowGroup);
    1519             : 
    1520         374 :                 if (iXMinField >= 0 && iYMinField >= 0 && iXMaxField >= 0 &&
    1521             :                     iYMaxField >= 0)
    1522             :                 {
    1523         142 :                     if (GetMinMaxForParquetCol(iRowGroup, iXMinField, nullptr,
    1524             :                                                true, sMin, bFoundMin, false,
    1525             :                                                sMax, bFoundMax, eType, eSubType,
    1526         141 :                                                osMinTmp, osMaxTmp) &&
    1527         283 :                         bFoundMin && eType == OFTReal)
    1528             :                     {
    1529         141 :                         const double dfGroupMinX = sMin.Real;
    1530         141 :                         if (dfGroupMinX > m_sFilterEnvelope.MaxX)
    1531             :                         {
    1532           1 :                             bSelectGroup = false;
    1533             :                         }
    1534         140 :                         else if (GetMinMaxForParquetCol(
    1535             :                                      iRowGroup, iYMinField, nullptr, true, sMin,
    1536             :                                      bFoundMin, false, sMax, bFoundMax, eType,
    1537         140 :                                      eSubType, osMinTmp, osMaxTmp) &&
    1538         280 :                                  bFoundMin && eType == OFTReal)
    1539             :                         {
    1540         140 :                             const double dfGroupMinY = sMin.Real;
    1541         140 :                             if (dfGroupMinY > m_sFilterEnvelope.MaxY)
    1542             :                             {
    1543           1 :                                 bSelectGroup = false;
    1544             :                             }
    1545         139 :                             else if (GetMinMaxForParquetCol(
    1546             :                                          iRowGroup, iXMaxField, nullptr, false,
    1547             :                                          sMin, bFoundMin, true, sMax, bFoundMax,
    1548         139 :                                          eType, eSubType, osMinTmp, osMaxTmp) &&
    1549         278 :                                      bFoundMax && eType == OFTReal)
    1550             :                             {
    1551         139 :                                 const double dfGroupMaxX = sMax.Real;
    1552         139 :                                 if (dfGroupMaxX < m_sFilterEnvelope.MinX)
    1553             :                                 {
    1554           1 :                                     bSelectGroup = false;
    1555             :                                 }
    1556         138 :                                 else if (GetMinMaxForParquetCol(
    1557             :                                              iRowGroup, iYMaxField, nullptr,
    1558             :                                              false, sMin, bFoundMin, true, sMax,
    1559             :                                              bFoundMax, eType, eSubType,
    1560         138 :                                              osMinTmp, osMaxTmp) &&
    1561         276 :                                          bFoundMax && eType == OFTReal)
    1562             :                                 {
    1563         138 :                                     const double dfGroupMaxY = sMax.Real;
    1564         138 :                                     if (dfGroupMaxY < m_sFilterEnvelope.MinY)
    1565             :                                     {
    1566           1 :                                         bSelectGroup = false;
    1567             :                                     }
    1568             :                                 }
    1569             :                             }
    1570             :                         }
    1571             :                     }
    1572             :                 }
    1573             : 
    1574         374 :                 if (bSelectGroup)
    1575             :                 {
    1576         519 :                     for (auto &constraint : m_asAttributeFilterConstraints)
    1577             :                     {
    1578         232 :                         int iOGRField = constraint.iField;
    1579         464 :                         if (constraint.iField ==
    1580         232 :                             m_poFeatureDefn->GetFieldCount() + SPF_FID)
    1581             :                         {
    1582           5 :                             iOGRField = OGR_FID_INDEX;
    1583             :                         }
    1584         232 :                         if (constraint.nOperation != SWQ_ISNULL &&
    1585         223 :                             constraint.nOperation != SWQ_ISNOTNULL)
    1586             :                         {
    1587         214 :                             if (iOGRField == OGR_FID_INDEX &&
    1588           5 :                                 m_iFIDParquetColumn < 0)
    1589             :                             {
    1590           4 :                                 sMin.Integer64 = nFeatureIdxTotal;
    1591           4 :                                 sMax.Integer64 =
    1592           4 :                                     nFeatureIdxTotal +
    1593           4 :                                     poRowGroup->metadata()->num_rows() - 1;
    1594           4 :                                 eType = OFTInteger64;
    1595             :                             }
    1596         210 :                             else if (!GetMinMaxForOGRField(
    1597             :                                          iRowGroup, iOGRField, true, sMin,
    1598             :                                          bFoundMin, true, sMax, bFoundMax,
    1599         207 :                                          eType, eSubType, osMinTmp, osMaxTmp) ||
    1600         210 :                                      !bFoundMin || !bFoundMax)
    1601             :                             {
    1602           3 :                                 bIterateEverything = true;
    1603           3 :                                 break;
    1604             :                             }
    1605             :                         }
    1606             : 
    1607         229 :                         IsConstraintPossibleRes res =
    1608             :                             IsConstraintPossibleRes::UNKNOWN;
    1609         229 :                         if (constraint.eType ==
    1610         127 :                                 OGRArrowLayer::Constraint::Type::Integer &&
    1611         127 :                             eType == OFTInteger)
    1612             :                         {
    1613             : #if 0
    1614             :                             CPLDebug("PARQUET",
    1615             :                                      "Group %d, field %s, min = %d, max = %d",
    1616             :                                      iRowGroup,
    1617             :                                      iOGRField == OGR_FID_INDEX
    1618             :                                          ? m_osFIDColumn.c_str()
    1619             :                                          : m_poFeatureDefn->GetFieldDefn(iOGRField)
    1620             :                                                ->GetNameRef(),
    1621             :                                      sMin.Integer, sMax.Integer);
    1622             : #endif
    1623         109 :                             res = IsConstraintPossible(
    1624             :                                 constraint.nOperation,
    1625             :                                 constraint.sValue.Integer, sMin.Integer,
    1626             :                                 sMax.Integer);
    1627             :                         }
    1628         120 :                         else if (constraint.eType == OGRArrowLayer::Constraint::
    1629          35 :                                                          Type::Integer64 &&
    1630          35 :                                  eType == OFTInteger64)
    1631             :                         {
    1632             : #if 0
    1633             :                             CPLDebug("PARQUET",
    1634             :                                      "Group %d, field %s, min = " CPL_FRMT_GIB
    1635             :                                      ", max = " CPL_FRMT_GIB,
    1636             :                                      iRowGroup,
    1637             :                                      iOGRField == OGR_FID_INDEX
    1638             :                                          ? m_osFIDColumn.c_str()
    1639             :                                          : m_poFeatureDefn->GetFieldDefn(iOGRField)
    1640             :                                                ->GetNameRef(),
    1641             :                                      static_cast<GIntBig>(sMin.Integer64),
    1642             :                                      static_cast<GIntBig>(sMax.Integer64));
    1643             : #endif
    1644          35 :                             res = IsConstraintPossible(
    1645             :                                 constraint.nOperation,
    1646             :                                 constraint.sValue.Integer64, sMin.Integer64,
    1647             :                                 sMax.Integer64);
    1648             :                         }
    1649          85 :                         else if (constraint.eType ==
    1650          29 :                                      OGRArrowLayer::Constraint::Type::Real &&
    1651          29 :                                  eType == OFTReal)
    1652             :                         {
    1653             : #if 0
    1654             :                             CPLDebug("PARQUET",
    1655             :                                      "Group %d, field %s, min = %g, max = %g",
    1656             :                                      iRowGroup,
    1657             :                                      iOGRField == OGR_FID_INDEX
    1658             :                                          ? m_osFIDColumn.c_str()
    1659             :                                          : m_poFeatureDefn->GetFieldDefn(iOGRField)
    1660             :                                                ->GetNameRef(),
    1661             :                                      sMin.Real, sMax.Real);
    1662             : #endif
    1663          26 :                             res = IsConstraintPossible(constraint.nOperation,
    1664             :                                                        constraint.sValue.Real,
    1665             :                                                        sMin.Real, sMax.Real);
    1666             :                         }
    1667          59 :                         else if (constraint.eType ==
    1668          38 :                                      OGRArrowLayer::Constraint::Type::String &&
    1669          38 :                                  eType == OFTString)
    1670             :                         {
    1671             : #if 0
    1672             :                             CPLDebug("PARQUET",
    1673             :                                      "Group %d, field %s, min = %s, max = %s",
    1674             :                                      iRowGroup,
    1675             :                                      iOGRField == OGR_FID_INDEX
    1676             :                                          ? m_osFIDColumn.c_str()
    1677             :                                          : m_poFeatureDefn->GetFieldDefn(iOGRField)
    1678             :                                                ->GetNameRef(),
    1679             :                                      sMin.String, sMax.String);
    1680             : #endif
    1681          38 :                             res = IsConstraintPossible(
    1682             :                                 constraint.nOperation,
    1683          76 :                                 std::string(constraint.sValue.String),
    1684          76 :                                 std::string(sMin.String),
    1685          76 :                                 std::string(sMax.String));
    1686             :                         }
    1687          21 :                         else if (constraint.nOperation == SWQ_ISNULL ||
    1688          12 :                                  constraint.nOperation == SWQ_ISNOTNULL)
    1689             :                         {
    1690             :                             const int iCol =
    1691             :                                 iOGRField == OGR_FID_INDEX
    1692          36 :                                     ? m_iFIDParquetColumn
    1693          18 :                                     : GetMapFieldIndexToParquetColumn()
    1694          18 :                                           [iOGRField];
    1695          18 :                             if (iCol >= 0)
    1696             :                             {
    1697             :                                 const auto metadata =
    1698          18 :                                     m_poArrowReader->parquet_reader()
    1699          36 :                                         ->metadata();
    1700             :                                 const auto rowGroupColumnChunk =
    1701          18 :                                     metadata->RowGroup(iRowGroup)->ColumnChunk(
    1702          36 :                                         iCol);
    1703             :                                 const auto rowGroupStats =
    1704          36 :                                     rowGroupColumnChunk->statistics();
    1705          36 :                                 if (rowGroupColumnChunk->is_stats_set() &&
    1706          18 :                                     rowGroupStats)
    1707             :                                 {
    1708          18 :                                     res = IsConstraintPossibleRes::YES;
    1709          27 :                                     if (constraint.nOperation == SWQ_ISNULL &&
    1710           9 :                                         rowGroupStats->num_values() ==
    1711           9 :                                             poRowGroup->metadata()->num_rows())
    1712             :                                     {
    1713           5 :                                         res = IsConstraintPossibleRes::NO;
    1714             :                                     }
    1715          26 :                                     else if (constraint.nOperation ==
    1716          22 :                                                  SWQ_ISNOTNULL &&
    1717           9 :                                              rowGroupStats->num_values() == 0)
    1718             :                                     {
    1719           1 :                                         res = IsConstraintPossibleRes::NO;
    1720             :                                     }
    1721             :                                 }
    1722          18 :                             }
    1723             :                         }
    1724             :                         else
    1725             :                         {
    1726           3 :                             CPLDebug(
    1727             :                                 "PARQUET",
    1728             :                                 "Unhandled combination of constraint.eType "
    1729             :                                 "(%d) and eType (%d)",
    1730           3 :                                 static_cast<int>(constraint.eType), eType);
    1731             :                         }
    1732             : 
    1733         229 :                         if (res == IsConstraintPossibleRes::NO)
    1734             :                         {
    1735          77 :                             bSelectGroup = false;
    1736          77 :                             break;
    1737             :                         }
    1738         152 :                         else if (res == IsConstraintPossibleRes::UNKNOWN)
    1739             :                         {
    1740           3 :                             bIterateEverything = true;
    1741           3 :                             break;
    1742             :                         }
    1743             :                     }
    1744             :                 }
    1745             : 
    1746         374 :                 if (bSelectGroup)
    1747             :                 {
    1748             :                     // CPLDebug("PARQUET", "Selecting row group %d", iRowGroup);
    1749             :                     m_asFeatureIdxRemapping.emplace_back(
    1750         293 :                         std::make_pair(nFeatureIdxSelected, nFeatureIdxTotal));
    1751         293 :                     anSelectedGroups.push_back(iRowGroup);
    1752         293 :                     nFeatureIdxSelected += poRowGroup->metadata()->num_rows();
    1753             :                 }
    1754             : 
    1755         374 :                 nFeatureIdxTotal += poRowGroup->metadata()->num_rows();
    1756             :             }
    1757             :         }
    1758             : 
    1759         722 :         if (bIterateEverything)
    1760             :         {
    1761         470 :             m_asFeatureIdxRemapping.clear();
    1762         470 :             m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
    1763         470 :             if (!CreateRecordBatchReader(0))
    1764           0 :                 return false;
    1765             :         }
    1766             :         else
    1767             :         {
    1768         252 :             m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
    1769         252 :             if (anSelectedGroups.empty())
    1770             :             {
    1771          10 :                 return false;
    1772             :             }
    1773         242 :             CPLDebug("PARQUET", "%d/%d row groups selected",
    1774         242 :                      int(anSelectedGroups.size()),
    1775         242 :                      m_poArrowReader->num_row_groups());
    1776         242 :             m_nFeatureIdx = m_oFeatureIdxRemappingIter->second;
    1777         242 :             ++m_oFeatureIdxRemappingIter;
    1778         242 :             if (!CreateRecordBatchReader(anSelectedGroups))
    1779             :             {
    1780           0 :                 return false;
    1781             :             }
    1782             :         }
    1783             :     }
    1784             : 
    1785        2804 :     std::shared_ptr<arrow::RecordBatch> poNextBatch;
    1786             : 
    1787           0 :     do
    1788             :     {
    1789        1402 :         ++m_iRecordBatch;
    1790        1402 :         poNextBatch.reset();
    1791        1402 :         auto status = m_poRecordBatchReader->ReadNext(&poNextBatch);
    1792        1402 :         if (!status.ok())
    1793             :         {
    1794           0 :             CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
    1795           0 :                      status.message().c_str());
    1796           0 :             poNextBatch.reset();
    1797             :         }
    1798        1402 :         if (poNextBatch == nullptr)
    1799             :         {
    1800         688 :             if (m_iRecordBatch == 1 && m_poBatch && m_poAttrQuery == nullptr &&
    1801         213 :                 m_poFilterGeom == nullptr)
    1802             :             {
    1803          43 :                 m_iRecordBatch = 0;
    1804          43 :                 m_bSingleBatch = true;
    1805             :             }
    1806             :             else
    1807         432 :                 m_poBatch.reset();
    1808         475 :             return false;
    1809             :         }
    1810         927 :     } while (poNextBatch->num_rows() == 0);
    1811             : 
    1812         927 :     SetBatch(poNextBatch);
    1813             : 
    1814             : #ifdef DEBUG
    1815         927 :     const auto &poColumns = m_poBatch->columns();
    1816             : 
    1817             :     // Sanity checks
    1818         927 :     CPLAssert(m_poBatch->num_columns() == (m_bIgnoredFields
    1819             :                                                ? m_nExpectedBatchColumns
    1820             :                                                : m_poSchema->num_fields()));
    1821             : 
    1822       42887 :     for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
    1823             :     {
    1824             :         int iCol;
    1825       41960 :         if (m_bIgnoredFields)
    1826             :         {
    1827       14050 :             iCol = m_anMapFieldIndexToArrayIndex[i];
    1828       14050 :             if (iCol < 0)
    1829        3509 :                 continue;
    1830             :         }
    1831             :         else
    1832             :         {
    1833       27910 :             iCol = m_anMapFieldIndexToArrowColumn[i][0];
    1834             :         }
    1835       38451 :         CPL_IGNORE_RET_VAL(iCol);  // to make cppcheck happy
    1836             : 
    1837       38451 :         CPLAssert(iCol < static_cast<int>(poColumns.size()));
    1838       38451 :         CPLAssert(m_poSchema->fields()[m_anMapFieldIndexToArrowColumn[i][0]]
    1839             :                       ->type()
    1840             :                       ->id() == poColumns[iCol]->type_id());
    1841             :     }
    1842             : 
    1843        1863 :     for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i)
    1844             :     {
    1845             :         int iCol;
    1846         936 :         if (m_bIgnoredFields)
    1847             :         {
    1848         275 :             iCol = m_anMapGeomFieldIndexToArrayIndex[i];
    1849         275 :             if (iCol < 0)
    1850          19 :                 continue;
    1851             :         }
    1852             :         else
    1853             :         {
    1854         661 :             iCol = m_anMapGeomFieldIndexToArrowColumn[i];
    1855             :         }
    1856         917 :         CPL_IGNORE_RET_VAL(iCol);  // to make cppcheck happy
    1857             : 
    1858         917 :         CPLAssert(iCol < static_cast<int>(poColumns.size()));
    1859         917 :         CPLAssert(m_poSchema->fields()[m_anMapGeomFieldIndexToArrowColumn[i]]
    1860             :                       ->type()
    1861             :                       ->id() == poColumns[iCol]->type_id());
    1862             :     }
    1863             : #endif
    1864             : 
    1865         927 :     return true;
    1866             : }
    1867             : 
    1868             : /************************************************************************/
    1869             : /*                     InvalidateCachedBatches()                        */
    1870             : /************************************************************************/
    1871             : 
    1872         775 : void OGRParquetLayer::InvalidateCachedBatches()
    1873             : {
    1874         775 :     m_iRecordBatch = -1;
    1875         775 :     m_bSingleBatch = false;
    1876         775 :     ResetReading();
    1877         775 : }
    1878             : 
    1879             : /************************************************************************/
    1880             : /*                        SetIgnoredFields()                            */
    1881             : /************************************************************************/
    1882             : 
    1883         219 : OGRErr OGRParquetLayer::SetIgnoredFields(CSLConstList papszFields)
    1884             : {
    1885         219 :     m_bIgnoredFields = false;
    1886         219 :     m_anRequestedParquetColumns.clear();
    1887         219 :     m_anMapFieldIndexToArrayIndex.clear();
    1888         219 :     m_anMapGeomFieldIndexToArrayIndex.clear();
    1889         219 :     m_nRequestedFIDColumn = -1;
    1890         219 :     OGRErr eErr = OGRLayer::SetIgnoredFields(papszFields);
    1891         219 :     if (!m_bHasMissingMappingToParquet && eErr == OGRERR_NONE)
    1892             :     {
    1893         219 :         m_bIgnoredFields = papszFields != nullptr && papszFields[0] != nullptr;
    1894         219 :         if (m_bIgnoredFields)
    1895             :         {
    1896         169 :             int nBatchColumns = 0;
    1897         169 :             if (m_iFIDParquetColumn >= 0)
    1898             :             {
    1899           6 :                 m_nRequestedFIDColumn = nBatchColumns;
    1900           6 :                 nBatchColumns++;
    1901           6 :                 m_anRequestedParquetColumns.push_back(m_iFIDParquetColumn);
    1902             :             }
    1903             : 
    1904        5993 :             for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
    1905             :             {
    1906             :                 const auto eArrowType =
    1907        5824 :                     m_poSchema->fields()[m_anMapFieldIndexToArrowColumn[i][0]]
    1908        5824 :                         ->type()
    1909        5824 :                         ->id();
    1910        5824 :                 if (eArrowType == arrow::Type::STRUCT)
    1911             :                 {
    1912             :                     // For a struct, for the sake of simplicity in
    1913             :                     // GetNextRawFeature(), as soon as one of the member if
    1914             :                     // requested, request all Parquet columns, so that the Arrow
    1915             :                     // type doesn't change
    1916          70 :                     bool bFoundNotIgnored = false;
    1917         310 :                     for (int j = i; j < m_poFeatureDefn->GetFieldCount() &&
    1918         308 :                                     m_anMapFieldIndexToArrowColumn[i][0] ==
    1919         154 :                                         m_anMapFieldIndexToArrowColumn[j][0];
    1920             :                          ++j)
    1921             :                     {
    1922         142 :                         if (!m_poFeatureDefn->GetFieldDefn(j)->IsIgnored())
    1923             :                         {
    1924          56 :                             bFoundNotIgnored = true;
    1925          56 :                             break;
    1926             :                         }
    1927             :                     }
    1928          70 :                     if (bFoundNotIgnored)
    1929             :                     {
    1930             :                         int j;
    1931         784 :                         for (j = i; j < m_poFeatureDefn->GetFieldCount() &&
    1932         784 :                                     m_anMapFieldIndexToArrowColumn[i][0] ==
    1933         392 :                                         m_anMapFieldIndexToArrowColumn[j][0];
    1934             :                              ++j)
    1935             :                         {
    1936             :                             const int iParquetCol =
    1937         336 :                                 m_anMapFieldIndexToParquetColumn[j];
    1938         336 :                             CPLAssert(iParquetCol >= 0);
    1939         336 :                             if (!m_poFeatureDefn->GetFieldDefn(j)->IsIgnored())
    1940             :                             {
    1941         330 :                                 m_anMapFieldIndexToArrayIndex.push_back(
    1942             :                                     nBatchColumns);
    1943             :                             }
    1944             :                             else
    1945             :                             {
    1946           6 :                                 m_anMapFieldIndexToArrayIndex.push_back(-1);
    1947             :                             }
    1948         336 :                             m_anRequestedParquetColumns.push_back(iParquetCol);
    1949             :                         }
    1950          56 :                         i = j - 1;
    1951          56 :                         nBatchColumns++;
    1952             :                     }
    1953             :                     else
    1954             :                     {
    1955             :                         int j;
    1956         186 :                         for (j = i; j < m_poFeatureDefn->GetFieldCount() &&
    1957         184 :                                     m_anMapFieldIndexToArrowColumn[i][0] ==
    1958          92 :                                         m_anMapFieldIndexToArrowColumn[j][0];
    1959             :                              ++j)
    1960             :                         {
    1961          80 :                             m_anMapFieldIndexToArrayIndex.push_back(-1);
    1962             :                         }
    1963          14 :                         i = j - 1;
    1964             :                     }
    1965             :                 }
    1966        5754 :                 else if (!m_poFeatureDefn->GetFieldDefn(i)->IsIgnored())
    1967             :                 {
    1968        4179 :                     const int iParquetCol = m_anMapFieldIndexToParquetColumn[i];
    1969        4179 :                     CPLAssert(iParquetCol >= 0);
    1970        4179 :                     m_anMapFieldIndexToArrayIndex.push_back(nBatchColumns);
    1971        4179 :                     nBatchColumns++;
    1972        4179 :                     m_anRequestedParquetColumns.push_back(iParquetCol);
    1973        4179 :                     if (eArrowType == arrow::Type::MAP)
    1974             :                     {
    1975             :                         // For a map, request both keys and items Parquet
    1976             :                         // columns
    1977         338 :                         m_anRequestedParquetColumns.push_back(iParquetCol + 1);
    1978             :                     }
    1979             :                 }
    1980             :                 else
    1981             :                 {
    1982        1575 :                     m_anMapFieldIndexToArrayIndex.push_back(-1);
    1983             :                 }
    1984             :             }
    1985             : 
    1986         169 :             CPLAssert(static_cast<int>(m_anMapFieldIndexToArrayIndex.size()) ==
    1987             :                       m_poFeatureDefn->GetFieldCount());
    1988             : 
    1989         350 :             for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i)
    1990             :             {
    1991         181 :                 if (!m_poFeatureDefn->GetGeomFieldDefn(i)->IsIgnored())
    1992             :                 {
    1993             :                     const auto &anVals =
    1994         163 :                         m_anMapGeomFieldIndexToParquetColumns[i];
    1995         163 :                     CPLAssert(!anVals.empty() && anVals[0] >= 0);
    1996             :                     m_anRequestedParquetColumns.insert(
    1997         163 :                         m_anRequestedParquetColumns.end(), anVals.begin(),
    1998         326 :                         anVals.end());
    1999         163 :                     m_anMapGeomFieldIndexToArrayIndex.push_back(nBatchColumns);
    2000         163 :                     nBatchColumns++;
    2001             : 
    2002         163 :                     auto oIter = m_oMapGeomFieldIndexToGeomColBBOX.find(i);
    2003             :                     const auto oIterParquet =
    2004         163 :                         m_oMapGeomFieldIndexToGeomColBBOXParquet.find(i);
    2005         226 :                     if (oIter != m_oMapGeomFieldIndexToGeomColBBOX.end() &&
    2006          63 :                         oIterParquet !=
    2007         226 :                             m_oMapGeomFieldIndexToGeomColBBOXParquet.end())
    2008             :                     {
    2009             :                         const bool bIsGeoArrowStruct =
    2010          63 :                             (m_aeGeomEncoding[i] ==
    2011          59 :                                  OGRArrowGeomEncoding::GEOARROW_STRUCT_POINT ||
    2012          59 :                              m_aeGeomEncoding[i] ==
    2013             :                                  OGRArrowGeomEncoding::
    2014          55 :                                      GEOARROW_STRUCT_LINESTRING ||
    2015          55 :                              m_aeGeomEncoding[i] ==
    2016             :                                  OGRArrowGeomEncoding::
    2017          49 :                                      GEOARROW_STRUCT_POLYGON ||
    2018          49 :                              m_aeGeomEncoding[i] ==
    2019             :                                  OGRArrowGeomEncoding::
    2020          45 :                                      GEOARROW_STRUCT_MULTIPOINT ||
    2021          45 :                              m_aeGeomEncoding[i] ==
    2022             :                                  OGRArrowGeomEncoding::
    2023         122 :                                      GEOARROW_STRUCT_MULTILINESTRING ||
    2024          41 :                              m_aeGeomEncoding[i] ==
    2025             :                                  OGRArrowGeomEncoding::
    2026          63 :                                      GEOARROW_STRUCT_MULTIPOLYGON);
    2027          63 :                         if (!bIsGeoArrowStruct)
    2028             :                         {
    2029          35 :                             oIter->second.iArrayIdx = nBatchColumns++;
    2030             :                             m_anRequestedParquetColumns.insert(
    2031          35 :                                 m_anRequestedParquetColumns.end(),
    2032          35 :                                 oIterParquet->second.anParquetCols.begin(),
    2033         105 :                                 oIterParquet->second.anParquetCols.end());
    2034             :                         }
    2035             :                     }
    2036             :                 }
    2037             :                 else
    2038             :                 {
    2039          18 :                     m_anMapGeomFieldIndexToArrayIndex.push_back(-1);
    2040             :                 }
    2041             :             }
    2042             : 
    2043         169 :             CPLAssert(
    2044             :                 static_cast<int>(m_anMapGeomFieldIndexToArrayIndex.size()) ==
    2045             :                 m_poFeatureDefn->GetGeomFieldCount());
    2046             : #ifdef DEBUG
    2047         169 :             m_nExpectedBatchColumns = nBatchColumns;
    2048             : #endif
    2049             :         }
    2050             :     }
    2051             : 
    2052         219 :     ComputeConstraintsArrayIdx();
    2053             : 
    2054             :     // Full invalidation
    2055         219 :     InvalidateCachedBatches();
    2056             : 
    2057         219 :     return eErr;
    2058             : }
    2059             : 
    2060             : /************************************************************************/
    2061             : /*                        GetFeatureCount()                             */
    2062             : /************************************************************************/
    2063             : 
    2064         666 : GIntBig OGRParquetLayer::GetFeatureCount(int bForce)
    2065             : {
    2066         666 :     if (m_poAttrQuery == nullptr && m_poFilterGeom == nullptr)
    2067             :     {
    2068          27 :         auto metadata = m_poArrowReader->parquet_reader()->metadata();
    2069          27 :         if (metadata)
    2070          27 :             return metadata->num_rows();
    2071             :     }
    2072         639 :     return OGRLayer::GetFeatureCount(bForce);
    2073             : }
    2074             : 
    2075             : /************************************************************************/
    2076             : /*                         FastGetExtent()                              */
    2077             : /************************************************************************/
    2078             : 
    2079         570 : bool OGRParquetLayer::FastGetExtent(int iGeomField, OGREnvelope *psExtent) const
    2080             : {
    2081         570 :     if (OGRParquetLayerBase::FastGetExtent(iGeomField, psExtent))
    2082         555 :         return true;
    2083             : 
    2084             :     const auto oIterToGeomColBBOX =
    2085          15 :         m_oMapGeomFieldIndexToGeomColBBOXParquet.find(iGeomField);
    2086          16 :     if (oIterToGeomColBBOX != m_oMapGeomFieldIndexToGeomColBBOXParquet.end() &&
    2087           1 :         CPLTestBool(CPLGetConfigOption("OGR_PARQUET_USE_BBOX", "YES")))
    2088             :     {
    2089           1 :         OGREnvelope sExtent;
    2090             :         OGRField sMin, sMax;
    2091           1 :         OGR_RawField_SetNull(&sMin);
    2092           1 :         OGR_RawField_SetNull(&sMax);
    2093             :         bool bFoundMin, bFoundMax;
    2094           1 :         OGRFieldType eType = OFTMaxType;
    2095           1 :         OGRFieldSubType eSubType = OFSTNone;
    2096           1 :         std::string osMinTmp, osMaxTmp;
    2097           2 :         if (GetMinMaxForParquetCol(-1, oIterToGeomColBBOX->second.iParquetXMin,
    2098             :                                    nullptr, true, sMin, bFoundMin, false, sMax,
    2099             :                                    bFoundMax, eType, eSubType, osMinTmp,
    2100           3 :                                    osMaxTmp) &&
    2101           1 :             eType == OFTReal)
    2102             :         {
    2103           1 :             sExtent.MinX = sMin.Real;
    2104             : 
    2105           1 :             if (GetMinMaxForParquetCol(
    2106           1 :                     -1, oIterToGeomColBBOX->second.iParquetYMin, nullptr, true,
    2107             :                     sMin, bFoundMin, false, sMax, bFoundMax, eType, eSubType,
    2108           3 :                     osMinTmp, osMaxTmp) &&
    2109           1 :                 eType == OFTReal)
    2110             :             {
    2111           1 :                 sExtent.MinY = sMin.Real;
    2112             : 
    2113           1 :                 if (GetMinMaxForParquetCol(
    2114           1 :                         -1, oIterToGeomColBBOX->second.iParquetXMax, nullptr,
    2115             :                         false, sMin, bFoundMin, true, sMax, bFoundMax, eType,
    2116           3 :                         eSubType, osMinTmp, osMaxTmp) &&
    2117           1 :                     eType == OFTReal)
    2118             :                 {
    2119           1 :                     sExtent.MaxX = sMax.Real;
    2120             : 
    2121           1 :                     if (GetMinMaxForParquetCol(
    2122           1 :                             -1, oIterToGeomColBBOX->second.iParquetYMax,
    2123             :                             nullptr, false, sMin, bFoundMin, true, sMax,
    2124           3 :                             bFoundMax, eType, eSubType, osMinTmp, osMaxTmp) &&
    2125           1 :                         eType == OFTReal)
    2126             :                     {
    2127           1 :                         sExtent.MaxY = sMax.Real;
    2128             : 
    2129           1 :                         CPLDebug("PARQUET",
    2130             :                                  "Using statistics of bbox.minx, bbox.miny, "
    2131             :                                  "bbox.maxx, bbox.maxy columns to get extent");
    2132           1 :                         m_oMapExtents[iGeomField] = sExtent;
    2133           1 :                         *psExtent = sExtent;
    2134           1 :                         return true;
    2135             :                     }
    2136             :                 }
    2137             :             }
    2138             :         }
    2139             :     }
    2140             : 
    2141          14 :     return false;
    2142             : }
    2143             : 
    2144             : /************************************************************************/
    2145             : /*                         TestCapability()                             */
    2146             : /************************************************************************/
    2147             : 
    2148         437 : int OGRParquetLayer::TestCapability(const char *pszCap)
    2149             : {
    2150         437 :     if (EQUAL(pszCap, OLCFastFeatureCount))
    2151          79 :         return m_poAttrQuery == nullptr && m_poFilterGeom == nullptr;
    2152             : 
    2153         358 :     if (EQUAL(pszCap, OLCIgnoreFields))
    2154           8 :         return !m_bHasMissingMappingToParquet;
    2155             : 
    2156         350 :     return OGRParquetLayerBase::TestCapability(pszCap);
    2157             : }
    2158             : 
    2159             : /************************************************************************/
    2160             : /*                         GetMetadataItem()                            */
    2161             : /************************************************************************/
    2162             : 
    2163         197 : const char *OGRParquetLayer::GetMetadataItem(const char *pszName,
    2164             :                                              const char *pszDomain)
    2165             : {
    2166             :     // Mostly for unit test purposes
    2167         197 :     if (pszDomain != nullptr && EQUAL(pszDomain, "_PARQUET_"))
    2168             :     {
    2169           9 :         int nRowGroupIdx = -1;
    2170           9 :         int nColumn = -1;
    2171           9 :         if (EQUAL(pszName, "NUM_ROW_GROUPS"))
    2172             :         {
    2173           1 :             return CPLSPrintf("%d", m_poArrowReader->num_row_groups());
    2174             :         }
    2175           8 :         if (EQUAL(pszName, "CREATOR"))
    2176             :         {
    2177           4 :             return CPLSPrintf("%s", m_poArrowReader->parquet_reader()
    2178           4 :                                         ->metadata()
    2179           2 :                                         ->created_by()
    2180           2 :                                         .c_str());
    2181             :         }
    2182          12 :         else if (sscanf(pszName, "ROW_GROUPS[%d]", &nRowGroupIdx) == 1 &&
    2183           6 :                  strstr(pszName, ".NUM_ROWS"))
    2184             :         {
    2185             :             try
    2186             :             {
    2187             :                 auto poRowGroup =
    2188           6 :                     m_poArrowReader->parquet_reader()->RowGroup(nRowGroupIdx);
    2189           3 :                 if (poRowGroup == nullptr)
    2190           0 :                     return nullptr;
    2191           3 :                 return CPLSPrintf("%" PRId64,
    2192           3 :                                   poRowGroup->metadata()->num_rows());
    2193             :             }
    2194           0 :             catch (const std::exception &)
    2195             :             {
    2196             :             }
    2197             :         }
    2198           6 :         else if (sscanf(pszName, "ROW_GROUPS[%d].COLUMNS[%d]", &nRowGroupIdx,
    2199           6 :                         &nColumn) == 2 &&
    2200           3 :                  strstr(pszName, ".COMPRESSION"))
    2201             :         {
    2202             :             try
    2203             :             {
    2204             :                 auto poRowGroup =
    2205           6 :                     m_poArrowReader->parquet_reader()->RowGroup(nRowGroupIdx);
    2206           3 :                 if (poRowGroup == nullptr)
    2207           0 :                     return nullptr;
    2208           6 :                 auto poColumn = poRowGroup->metadata()->ColumnChunk(nColumn);
    2209           3 :                 return CPLSPrintf("%s", arrow::util::Codec::GetCodecAsString(
    2210           3 :                                             poColumn->compression())
    2211           3 :                                             .c_str());
    2212             :             }
    2213           0 :             catch (const std::exception &)
    2214             :             {
    2215             :             }
    2216             :         }
    2217           0 :         return nullptr;
    2218             :     }
    2219         188 :     if (pszDomain != nullptr && EQUAL(pszDomain, "_PARQUET_METADATA_"))
    2220             :     {
    2221         292 :         const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    2222         146 :         const auto &kv_metadata = metadata->key_value_metadata();
    2223         146 :         if (kv_metadata && kv_metadata->Contains(pszName))
    2224             :         {
    2225         143 :             auto metadataItem = kv_metadata->Get(pszName);
    2226         143 :             if (metadataItem.ok())
    2227             :             {
    2228         143 :                 return CPLSPrintf("%s", metadataItem->c_str());
    2229             :             }
    2230             :         }
    2231           3 :         return nullptr;
    2232             :     }
    2233          42 :     return OGRLayer::GetMetadataItem(pszName, pszDomain);
    2234             : }
    2235             : 
    2236             : /************************************************************************/
    2237             : /*                           GetMetadata()                              */
    2238             : /************************************************************************/
    2239             : 
    2240          28 : char **OGRParquetLayer::GetMetadata(const char *pszDomain)
    2241             : {
    2242             :     // Mostly for unit test purposes
    2243          28 :     if (pszDomain != nullptr && EQUAL(pszDomain, "_PARQUET_METADATA_"))
    2244             :     {
    2245           2 :         m_aosFeatherMetadata.Clear();
    2246           4 :         const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    2247           2 :         const auto &kv_metadata = metadata->key_value_metadata();
    2248           2 :         if (kv_metadata)
    2249             :         {
    2250           7 :             for (const auto &kv : kv_metadata->sorted_pairs())
    2251             :             {
    2252             :                 m_aosFeatherMetadata.SetNameValue(kv.first.c_str(),
    2253           5 :                                                   kv.second.c_str());
    2254             :             }
    2255             :         }
    2256           2 :         return m_aosFeatherMetadata.List();
    2257             :     }
    2258          26 :     return OGRLayer::GetMetadata(pszDomain);
    2259             : }
    2260             : 
    2261             : /************************************************************************/
    2262             : /*                          GetArrowStream()                            */
    2263             : /************************************************************************/
    2264             : 
    2265         131 : bool OGRParquetLayer::GetArrowStream(struct ArrowArrayStream *out_stream,
    2266             :                                      CSLConstList papszOptions)
    2267             : {
    2268             :     const char *pszMaxFeaturesInBatch =
    2269         131 :         CSLFetchNameValue(papszOptions, "MAX_FEATURES_IN_BATCH");
    2270         131 :     if (pszMaxFeaturesInBatch)
    2271             :     {
    2272           7 :         int nMaxBatchSize = atoi(pszMaxFeaturesInBatch);
    2273           7 :         if (nMaxBatchSize <= 0)
    2274           0 :             nMaxBatchSize = 1;
    2275           7 :         if (nMaxBatchSize > INT_MAX - 1)
    2276           0 :             nMaxBatchSize = INT_MAX - 1;
    2277           7 :         m_poArrowReader->set_batch_size(nMaxBatchSize);
    2278             :     }
    2279         131 :     return OGRArrowLayer::GetArrowStream(out_stream, papszOptions);
    2280             : }
    2281             : 
    2282             : /************************************************************************/
    2283             : /*                           SetNextByIndex()                           */
    2284             : /************************************************************************/
    2285             : 
    2286          12 : OGRErr OGRParquetLayer::SetNextByIndex(GIntBig nIndex)
    2287             : {
    2288          12 :     if (nIndex < 0)
    2289           3 :         return OGRERR_FAILURE;
    2290             : 
    2291          18 :     const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    2292           9 :     if (nIndex >= metadata->num_rows())
    2293           3 :         return OGRERR_FAILURE;
    2294             : 
    2295           6 :     if (m_bSingleBatch)
    2296             :     {
    2297           0 :         ResetReading();
    2298           0 :         m_nIdxInBatch = nIndex;
    2299           0 :         m_nFeatureIdx = nIndex;
    2300           0 :         return OGRERR_NONE;
    2301             :     }
    2302             : 
    2303           6 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    2304           6 :     int64_t nAccRows = 0;
    2305           6 :     const auto nBatchSize = m_poArrowReader->properties().batch_size();
    2306           6 :     m_iRecordBatch = -1;
    2307           6 :     ResetReading();
    2308           6 :     m_iRecordBatch = 0;
    2309           7 :     for (int iGroup = 0; iGroup < nNumGroups; ++iGroup)
    2310             :     {
    2311             :         const int64_t nNextAccRows =
    2312           7 :             nAccRows + metadata->RowGroup(iGroup)->num_rows();
    2313           7 :         if (nIndex < nNextAccRows)
    2314             :         {
    2315           6 :             if (!CreateRecordBatchReader(iGroup))
    2316           0 :                 return OGRERR_FAILURE;
    2317             : 
    2318          12 :             std::shared_ptr<arrow::RecordBatch> poBatch;
    2319             :             while (true)
    2320             :             {
    2321           6 :                 auto status = m_poRecordBatchReader->ReadNext(&poBatch);
    2322           6 :                 if (!status.ok())
    2323             :                 {
    2324           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    2325           0 :                              "ReadNext() failed: %s", status.message().c_str());
    2326           0 :                     m_iRecordBatch = -1;
    2327           0 :                     ResetReading();
    2328           0 :                     return OGRERR_FAILURE;
    2329             :                 }
    2330           6 :                 if (poBatch == nullptr)
    2331             :                 {
    2332           0 :                     m_iRecordBatch = -1;
    2333           0 :                     ResetReading();
    2334           0 :                     return OGRERR_FAILURE;
    2335             :                 }
    2336           6 :                 if (nIndex < nAccRows + poBatch->num_rows())
    2337             :                 {
    2338           6 :                     break;
    2339             :                 }
    2340           0 :                 nAccRows += poBatch->num_rows();
    2341           0 :                 m_iRecordBatch++;
    2342           0 :             }
    2343           6 :             m_nIdxInBatch = nIndex - nAccRows;
    2344           6 :             m_nFeatureIdx = nIndex;
    2345           6 :             SetBatch(poBatch);
    2346           6 :             return OGRERR_NONE;
    2347             :         }
    2348           1 :         nAccRows = nNextAccRows;
    2349           1 :         m_iRecordBatch +=
    2350           1 :             (metadata->RowGroup(iGroup)->num_rows() + nBatchSize - 1) /
    2351             :             nBatchSize;
    2352             :     }
    2353             : 
    2354           0 :     m_iRecordBatch = -1;
    2355           0 :     ResetReading();
    2356           0 :     return OGRERR_FAILURE;
    2357             : }
    2358             : 
    2359             : /***********************************************************************/
    2360             : /*                            GetStats()                               */
    2361             : /***********************************************************************/
    2362             : 
    2363             : template <class STAT_TYPE> struct GetStats
    2364             : {
    2365             :     using T = typename STAT_TYPE::T;
    2366             : 
    2367         489 :     static T min(const std::shared_ptr<parquet::FileMetaData> &metadata,
    2368             :                  const int iRowGroup, const int numRowGroups, const int iCol,
    2369             :                  bool &bFound)
    2370             :     {
    2371         489 :         T v{};
    2372         489 :         bFound = false;
    2373         986 :         for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
    2374             :         {
    2375         533 :             const auto columnChunk =
    2376          30 :                 metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
    2377             :                     ->ColumnChunk(iCol);
    2378         503 :             const auto colStats = columnChunk->statistics();
    2379        1003 :             if (columnChunk->is_stats_set() && colStats &&
    2380         500 :                 colStats->HasMinMax())
    2381             :             {
    2382         494 :                 auto castStats = static_cast<STAT_TYPE *>(colStats.get());
    2383         494 :                 const auto rowGroupVal = castStats->min();
    2384         494 :                 if (i == 0 || rowGroupVal < v)
    2385             :                 {
    2386         482 :                     bFound = true;
    2387         482 :                     v = rowGroupVal;
    2388             :                 }
    2389             :             }
    2390           9 :             else if (columnChunk->num_values() > 0)
    2391             :             {
    2392           6 :                 bFound = false;
    2393           6 :                 break;
    2394             :             }
    2395             :         }
    2396         489 :         return v;
    2397             :     }
    2398             : 
    2399         478 :     static T max(const std::shared_ptr<parquet::FileMetaData> &metadata,
    2400             :                  const int iRowGroup, const int numRowGroups, const int iCol,
    2401             :                  bool &bFound)
    2402             :     {
    2403         478 :         T v{};
    2404         478 :         bFound = false;
    2405         970 :         for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
    2406             :         {
    2407         522 :             const auto columnChunk =
    2408          30 :                 metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
    2409             :                     ->ColumnChunk(iCol);
    2410         492 :             const auto colStats = columnChunk->statistics();
    2411         982 :             if (columnChunk->is_stats_set() && colStats &&
    2412         490 :                 colStats->HasMinMax())
    2413             :             {
    2414         490 :                 auto castStats = static_cast<STAT_TYPE *>(colStats.get());
    2415         490 :                 const auto rowGroupVal = castStats->max();
    2416         490 :                 if (i == 0 || rowGroupVal > v)
    2417             :                 {
    2418         488 :                     bFound = true;
    2419         488 :                     v = rowGroupVal;
    2420             :                 }
    2421             :             }
    2422           2 :             else if (columnChunk->num_values() > 0)
    2423             :             {
    2424           0 :                 bFound = false;
    2425           0 :                 break;
    2426             :             }
    2427             :         }
    2428         478 :         return v;
    2429             :     }
    2430             : };
    2431             : 
    2432             : template <> struct GetStats<parquet::ByteArrayStatistics>
    2433             : {
    2434             :     static std::string
    2435          39 :     min(const std::shared_ptr<parquet::FileMetaData> &metadata,
    2436             :         const int iRowGroup, const int numRowGroups, const int iCol,
    2437             :         bool &bFound)
    2438             :     {
    2439          39 :         std::string v{};
    2440          39 :         bFound = false;
    2441          79 :         for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
    2442             :         {
    2443             :             const auto columnChunk =
    2444          40 :                 metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
    2445          80 :                     ->ColumnChunk(iCol);
    2446          80 :             const auto colStats = columnChunk->statistics();
    2447          80 :             if (columnChunk->is_stats_set() && colStats &&
    2448          40 :                 colStats->HasMinMax())
    2449             :             {
    2450             :                 auto castStats =
    2451          40 :                     static_cast<parquet::ByteArrayStatistics *>(colStats.get());
    2452          40 :                 const auto rowGroupValRaw = castStats->min();
    2453             :                 const std::string rowGroupVal(
    2454          40 :                     reinterpret_cast<const char *>(rowGroupValRaw.ptr),
    2455          80 :                     rowGroupValRaw.len);
    2456          40 :                 if (i == 0 || rowGroupVal < v)
    2457             :                 {
    2458          39 :                     bFound = true;
    2459          39 :                     v = rowGroupVal;
    2460             :                 }
    2461             :             }
    2462             :         }
    2463          39 :         return v;
    2464             :     }
    2465             : 
    2466             :     static std::string
    2467          39 :     max(const std::shared_ptr<parquet::FileMetaData> &metadata,
    2468             :         const int iRowGroup, const int numRowGroups, const int iCol,
    2469             :         bool &bFound)
    2470             :     {
    2471          39 :         std::string v{};
    2472          39 :         bFound = false;
    2473          79 :         for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
    2474             :         {
    2475             :             const auto columnChunk =
    2476          40 :                 metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
    2477          40 :                     ->ColumnChunk(iCol);
    2478          40 :             const auto colStats = columnChunk->statistics();
    2479          80 :             if (columnChunk->is_stats_set() && colStats &&
    2480          40 :                 colStats->HasMinMax())
    2481             :             {
    2482             :                 auto castStats =
    2483          40 :                     static_cast<parquet::ByteArrayStatistics *>(colStats.get());
    2484          40 :                 const auto rowGroupValRaw = castStats->max();
    2485             :                 const std::string rowGroupVal(
    2486          40 :                     reinterpret_cast<const char *>(rowGroupValRaw.ptr),
    2487          80 :                     rowGroupValRaw.len);
    2488          40 :                 if (i == 0 || rowGroupVal > v)
    2489             :                 {
    2490          40 :                     bFound = true;
    2491          40 :                     v = rowGroupVal;
    2492             :                 }
    2493             :             }
    2494             :             else
    2495             :             {
    2496           0 :                 bFound = false;
    2497           0 :                 break;
    2498             :             }
    2499             :         }
    2500          39 :         return v;
    2501             :     }
    2502             : };
    2503             : 
    2504             : /************************************************************************/
    2505             : /*                        GetMinMaxForOGRField()                        */
    2506             : /************************************************************************/
    2507             : 
    2508         240 : bool OGRParquetLayer::GetMinMaxForOGRField(int iRowGroup,  // -1 for all
    2509             :                                            int iOGRField, bool bComputeMin,
    2510             :                                            OGRField &sMin, bool &bFoundMin,
    2511             :                                            bool bComputeMax, OGRField &sMax,
    2512             :                                            bool &bFoundMax, OGRFieldType &eType,
    2513             :                                            OGRFieldSubType &eSubType,
    2514             :                                            std::string &osMinTmp,
    2515             :                                            std::string &osMaxTmp) const
    2516             : {
    2517         240 :     OGR_RawField_SetNull(&sMin);
    2518         240 :     OGR_RawField_SetNull(&sMax);
    2519         240 :     eType = OFTReal;
    2520         240 :     eSubType = OFSTNone;
    2521         240 :     bFoundMin = false;
    2522         240 :     bFoundMax = false;
    2523             : 
    2524             :     const int iCol = iOGRField == OGR_FID_INDEX
    2525         477 :                          ? m_iFIDParquetColumn
    2526         237 :                          : GetMapFieldIndexToParquetColumn()[iOGRField];
    2527         240 :     if (iCol < 0)
    2528           0 :         return false;
    2529             :     const auto &arrowType = iOGRField == OGR_FID_INDEX
    2530         240 :                                 ? m_poFIDType
    2531         237 :                                 : GetArrowFieldTypes()[iOGRField];
    2532             : 
    2533         240 :     const bool bRet = GetMinMaxForParquetCol(
    2534             :         iRowGroup, iCol, arrowType, bComputeMin, sMin, bFoundMin, bComputeMax,
    2535             :         sMax, bFoundMax, eType, eSubType, osMinTmp, osMaxTmp);
    2536             : 
    2537         240 :     if (eType == OFTInteger64 && arrowType->id() == arrow::Type::TIMESTAMP)
    2538             :     {
    2539             :         const OGRFieldDefn oDummyFIDFieldDefn(m_osFIDColumn.c_str(),
    2540           4 :                                               OFTInteger64);
    2541             :         const OGRFieldDefn *poFieldDefn =
    2542           2 :             iOGRField == OGR_FID_INDEX ? &oDummyFIDFieldDefn
    2543             :                                        : const_cast<OGRParquetLayer *>(this)
    2544           2 :                                              ->GetLayerDefn()
    2545           2 :                                              ->GetFieldDefn(iOGRField);
    2546           2 :         if (poFieldDefn->GetType() == OFTDateTime)
    2547             :         {
    2548             :             const auto timestampType =
    2549           2 :                 static_cast<arrow::TimestampType *>(arrowType.get());
    2550           2 :             if (bFoundMin)
    2551             :             {
    2552           1 :                 const int64_t timestamp = sMin.Integer64;
    2553           1 :                 OGRArrowLayer::TimestampToOGR(timestamp, timestampType,
    2554             :                                               poFieldDefn->GetTZFlag(), &sMin);
    2555             :             }
    2556           2 :             if (bFoundMax)
    2557             :             {
    2558           1 :                 const int64_t timestamp = sMax.Integer64;
    2559           1 :                 OGRArrowLayer::TimestampToOGR(timestamp, timestampType,
    2560             :                                               poFieldDefn->GetTZFlag(), &sMax);
    2561             :             }
    2562           2 :             eType = OFTDateTime;
    2563             :         }
    2564             :     }
    2565             : 
    2566         240 :     return bRet;
    2567             : }
    2568             : 
    2569             : /************************************************************************/
    2570             : /*                        GetMinMaxForParquetCol()                      */
    2571             : /************************************************************************/
    2572             : 
    2573         841 : bool OGRParquetLayer::GetMinMaxForParquetCol(
    2574             :     int iRowGroup,  // -1 for all
    2575             :     int iCol,
    2576             :     const std::shared_ptr<arrow::DataType> &arrowType,  // potentially nullptr
    2577             :     bool bComputeMin, OGRField &sMin, bool &bFoundMin, bool bComputeMax,
    2578             :     OGRField &sMax, bool &bFoundMax, OGRFieldType &eType,
    2579             :     OGRFieldSubType &eSubType, std::string &osMinTmp,
    2580             :     std::string &osMaxTmp) const
    2581             : {
    2582         841 :     OGR_RawField_SetNull(&sMin);
    2583         841 :     OGR_RawField_SetNull(&sMax);
    2584         841 :     eType = OFTReal;
    2585         841 :     eSubType = OFSTNone;
    2586         841 :     bFoundMin = false;
    2587         841 :     bFoundMax = false;
    2588             : 
    2589        1682 :     const auto metadata = GetReader()->parquet_reader()->metadata();
    2590         841 :     const auto numRowGroups = metadata->num_row_groups();
    2591             : 
    2592         841 :     if (numRowGroups == 0)
    2593           0 :         return false;
    2594             : 
    2595        1682 :     const auto rowGroup0 = metadata->RowGroup(0);
    2596         841 :     if (iCol < 0 || iCol >= rowGroup0->num_columns())
    2597             :     {
    2598           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2599             :                  "GetMinMaxForParquetCol(): invalid iCol=%d", iCol);
    2600           0 :         return false;
    2601             :     }
    2602        1682 :     const auto rowGroup0columnChunk = rowGroup0->ColumnChunk(iCol);
    2603        1682 :     const auto rowGroup0Stats = rowGroup0columnChunk->statistics();
    2604         841 :     if (!(rowGroup0columnChunk->is_stats_set() && rowGroup0Stats))
    2605             :     {
    2606           0 :         CPLDebug("PARQUET", "Statistics not available for field %s",
    2607           0 :                  rowGroup0columnChunk->path_in_schema()->ToDotString().c_str());
    2608           0 :         return false;
    2609             :     }
    2610             : 
    2611         841 :     const auto physicalType = rowGroup0Stats->physical_type();
    2612             : 
    2613         841 :     if (bComputeMin)
    2614             :     {
    2615         531 :         if (physicalType == parquet::Type::BOOLEAN)
    2616             :         {
    2617          50 :             eType = OFTInteger;
    2618          50 :             eSubType = OFSTBoolean;
    2619          50 :             sMin.Integer = GetStats<parquet::BoolStatistics>::min(
    2620             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    2621             :         }
    2622         481 :         else if (physicalType == parquet::Type::INT32)
    2623             :         {
    2624          66 :             if (arrowType && arrowType->id() == arrow::Type::UINT32)
    2625             :             {
    2626             :                 // With parquet file version 2.0,
    2627             :                 // statistics of uint32 fields are
    2628             :                 // stored as signed int32 values...
    2629           1 :                 eType = OFTInteger64;
    2630           1 :                 int nVal = GetStats<parquet::Int32Statistics>::min(
    2631             :                     metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    2632           1 :                 if (bFoundMin)
    2633             :                 {
    2634           1 :                     sMin.Integer64 = static_cast<uint32_t>(nVal);
    2635             :                 }
    2636             :             }
    2637             :             else
    2638             :             {
    2639          65 :                 eType = OFTInteger;
    2640          65 :                 if (arrowType && arrowType->id() == arrow::Type::INT16)
    2641           1 :                     eSubType = OFSTInt16;
    2642          65 :                 sMin.Integer = GetStats<parquet::Int32Statistics>::min(
    2643             :                     metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    2644             :             }
    2645             :         }
    2646         415 :         else if (physicalType == parquet::Type::INT64)
    2647             :         {
    2648          39 :             eType = OFTInteger64;
    2649          39 :             sMin.Integer64 = GetStats<parquet::Int64Statistics>::min(
    2650             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    2651             :         }
    2652         376 :         else if (physicalType == parquet::Type::FLOAT)
    2653             :         {
    2654         124 :             eType = OFTReal;
    2655         124 :             eSubType = OFSTFloat32;
    2656         124 :             sMin.Real = GetStats<parquet::FloatStatistics>::min(
    2657             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    2658             :         }
    2659         252 :         else if (physicalType == parquet::Type::DOUBLE)
    2660             :         {
    2661         210 :             eType = OFTReal;
    2662         210 :             sMin.Real = GetStats<parquet::DoubleStatistics>::min(
    2663             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    2664             :         }
    2665          42 :         else if (arrowType &&
    2666          53 :                  (arrowType->id() == arrow::Type::STRING ||
    2667          95 :                   arrowType->id() == arrow::Type::LARGE_STRING) &&
    2668             :                  physicalType == parquet::Type::BYTE_ARRAY)
    2669             :         {
    2670          78 :             osMinTmp = GetStats<parquet::ByteArrayStatistics>::min(
    2671          39 :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    2672          39 :             if (bFoundMin)
    2673             :             {
    2674          39 :                 eType = OFTString;
    2675          39 :                 sMin.String = &osMinTmp[0];
    2676             :             }
    2677             :         }
    2678             :     }
    2679             : 
    2680         841 :     if (bComputeMax)
    2681             :     {
    2682         520 :         if (physicalType == parquet::Type::BOOLEAN)
    2683             :         {
    2684          50 :             eType = OFTInteger;
    2685          50 :             eSubType = OFSTBoolean;
    2686          50 :             sMax.Integer = GetStats<parquet::BoolStatistics>::max(
    2687             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    2688             :         }
    2689         470 :         else if (physicalType == parquet::Type::INT32)
    2690             :         {
    2691          66 :             if (arrowType && arrowType->id() == arrow::Type::UINT32)
    2692             :             {
    2693             :                 // With parquet file version 2.0,
    2694             :                 // statistics of uint32 fields are
    2695             :                 // stored as signed int32 values...
    2696           1 :                 eType = OFTInteger64;
    2697           1 :                 int nVal = GetStats<parquet::Int32Statistics>::max(
    2698             :                     metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    2699           1 :                 if (bFoundMax)
    2700             :                 {
    2701           1 :                     sMax.Integer64 = static_cast<uint32_t>(nVal);
    2702             :                 }
    2703             :             }
    2704             :             else
    2705             :             {
    2706          65 :                 eType = OFTInteger;
    2707          65 :                 if (arrowType && arrowType->id() == arrow::Type::INT16)
    2708           1 :                     eSubType = OFSTInt16;
    2709          65 :                 sMax.Integer = GetStats<parquet::Int32Statistics>::max(
    2710             :                     metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    2711             :             }
    2712             :         }
    2713         404 :         else if (physicalType == parquet::Type::INT64)
    2714             :         {
    2715          39 :             eType = OFTInteger64;
    2716          39 :             sMax.Integer64 = GetStats<parquet::Int64Statistics>::max(
    2717             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    2718             :         }
    2719         365 :         else if (physicalType == parquet::Type::FLOAT)
    2720             :         {
    2721         114 :             eType = OFTReal;
    2722         114 :             eSubType = OFSTFloat32;
    2723         114 :             sMax.Real = GetStats<parquet::FloatStatistics>::max(
    2724             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    2725             :         }
    2726         251 :         else if (physicalType == parquet::Type::DOUBLE)
    2727             :         {
    2728         209 :             eType = OFTReal;
    2729         209 :             sMax.Real = GetStats<parquet::DoubleStatistics>::max(
    2730             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    2731             :         }
    2732          42 :         else if (arrowType &&
    2733          53 :                  (arrowType->id() == arrow::Type::STRING ||
    2734          95 :                   arrowType->id() == arrow::Type::LARGE_STRING) &&
    2735             :                  physicalType == parquet::Type::BYTE_ARRAY)
    2736             :         {
    2737          78 :             osMaxTmp = GetStats<parquet::ByteArrayStatistics>::max(
    2738          39 :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    2739          39 :             if (bFoundMax)
    2740             :             {
    2741          39 :                 eType = OFTString;
    2742          39 :                 sMax.String = &osMaxTmp[0];
    2743             :             }
    2744             :         }
    2745             :     }
    2746             : 
    2747         841 :     return bFoundMin || bFoundMax;
    2748             : }
    2749             : 
    2750             : /************************************************************************/
    2751             : /*                        GeomColsBBOXParquet()                         */
    2752             : /************************************************************************/
    2753             : 
    2754             : /** Return for a given geometry column (iGeom: in [0, GetGeomFieldCount()-1] range),
    2755             :  * the Parquet column number of the corresponding xmin,ymin,xmax,ymax bounding
    2756             :  * box columns, if existing.
    2757             :  */
    2758           1 : bool OGRParquetLayer::GeomColsBBOXParquet(int iGeom, int &iParquetXMin,
    2759             :                                           int &iParquetYMin, int &iParquetXMax,
    2760             :                                           int &iParquetYMax) const
    2761             : {
    2762           1 :     const auto oIter = m_oMapGeomFieldIndexToGeomColBBOXParquet.find(iGeom);
    2763             :     const bool bFound =
    2764           1 :         (oIter != m_oMapGeomFieldIndexToGeomColBBOXParquet.end());
    2765           1 :     if (bFound)
    2766             :     {
    2767           1 :         iParquetXMin = oIter->second.iParquetXMin;
    2768           1 :         iParquetYMin = oIter->second.iParquetYMin;
    2769           1 :         iParquetXMax = oIter->second.iParquetXMax;
    2770           1 :         iParquetYMax = oIter->second.iParquetYMax;
    2771             :     }
    2772           1 :     return bFound;
    2773             : }

Generated by: LCOV version 1.14