LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/parquet - ogrparquetlayer.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1341 1444 92.9 %
Date: 2026-02-12 23:49:34 Functions: 57 57 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             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_json.h"
      14             : #include "cpl_time.h"
      15             : #include "cpl_multiproc.h"
      16             : #include "gdal_pam.h"
      17             : #include "ogrsf_frmts.h"
      18             : #include "ogr_p.h"
      19             : #include "gdal_thread_pool.h"
      20             : 
      21             : #include <algorithm>
      22             : #include <cinttypes>
      23             : #include <cmath>
      24             : #include <limits>
      25             : #include <map>
      26             : #include <set>
      27             : #include <utility>
      28             : 
      29             : #include "ogr_parquet.h"
      30             : 
      31             : #include "../arrow_common/ograrrowlayer.hpp"
      32             : #include "../arrow_common/ograrrowdataset.hpp"
      33             : 
      34             : /************************************************************************/
      35             : /*                        OGRParquetLayerBase()                         */
      36             : /************************************************************************/
      37             : 
      38        1414 : OGRParquetLayerBase::OGRParquetLayerBase(OGRParquetDataset *poDS,
      39             :                                          const char *pszLayerName,
      40        1414 :                                          CSLConstList papszOpenOptions)
      41             :     : OGRArrowLayer(poDS, pszLayerName,
      42        1414 :                     CPLTestBool(CSLFetchNameValueDef(
      43             :                         papszOpenOptions, "LISTS_AS_STRING_JSON", "NO"))),
      44             :       m_poDS(poDS),
      45             :       m_aosGeomPossibleNames(CSLTokenizeString2(
      46             :           CSLFetchNameValueDef(papszOpenOptions, "GEOM_POSSIBLE_NAMES",
      47             :                                "geometry,wkb_geometry,wkt_geometry"),
      48             :           ",", 0)),
      49        2828 :       m_osCRS(CSLFetchNameValueDef(papszOpenOptions, "CRS", ""))
      50             : {
      51        1414 : }
      52             : 
      53             : /************************************************************************/
      54             : /*                             GetDataset()                             */
      55             : /************************************************************************/
      56             : 
      57          27 : GDALDataset *OGRParquetLayerBase::GetDataset()
      58             : {
      59          27 :     return m_poDS;
      60             : }
      61             : 
      62             : /************************************************************************/
      63             : /*                            ResetReading()                            */
      64             : /************************************************************************/
      65             : 
      66        8069 : void OGRParquetLayerBase::ResetReading()
      67             : {
      68        8069 :     if (m_iRecordBatch != 0)
      69             :     {
      70        7601 :         m_poRecordBatchReader.reset();
      71             :     }
      72        8069 :     OGRArrowLayer::ResetReading();
      73        8069 : }
      74             : 
      75             : /************************************************************************/
      76             : /*                      InvalidateCachedBatches()                       */
      77             : /************************************************************************/
      78             : 
      79        2208 : void OGRParquetLayerBase::InvalidateCachedBatches()
      80             : {
      81        2208 :     m_iRecordBatch = -1;
      82        2208 :     ResetReading();
      83        2208 : }
      84             : 
      85             : /************************************************************************/
      86             : /*                          LoadGeoMetadata()                           */
      87             : /************************************************************************/
      88             : 
      89        1414 : void OGRParquetLayerBase::LoadGeoMetadata(
      90             :     const std::shared_ptr<const arrow::KeyValueMetadata> &kv_metadata)
      91             : {
      92        1414 :     if (kv_metadata && kv_metadata->Contains("geo"))
      93             :     {
      94        2686 :         auto geo = kv_metadata->Get("geo");
      95        1343 :         if (geo.ok())
      96             :         {
      97        1343 :             CPLDebug("PARQUET", "geo = %s", geo->c_str());
      98        2686 :             CPLJSONDocument oDoc;
      99        1343 :             if (oDoc.LoadMemory(*geo))
     100             :             {
     101        2684 :                 auto oRoot = oDoc.GetRoot();
     102        4026 :                 const auto osVersion = oRoot.GetString("version");
     103        3278 :                 if (osVersion != "0.1.0" && osVersion != "0.2.0" &&
     104        2903 :                     osVersion != "0.3.0" && osVersion != "0.4.0" &&
     105        2899 :                     osVersion != "1.0.0-beta.1" && osVersion != "1.0.0-rc.1" &&
     106        3276 :                     osVersion != "1.0.0" && osVersion != "1.1.0")
     107             :                 {
     108           1 :                     CPLDebug(
     109             :                         "PARQUET",
     110             :                         "version = %s not explicitly handled by the driver",
     111             :                         osVersion.c_str());
     112             :                 }
     113             : 
     114        4026 :                 auto oColumns = oRoot.GetObj("columns");
     115        1342 :                 if (oColumns.IsValid())
     116             :                 {
     117        2699 :                     for (const auto &oColumn : oColumns.GetChildren())
     118             :                     {
     119        1358 :                         m_oMapGeometryColumns[oColumn.GetName()] = oColumn;
     120             :                     }
     121             :                 }
     122             :             }
     123             :             else
     124             :             {
     125           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
     126             :                          "Cannot parse 'geo' metadata");
     127             :             }
     128             :         }
     129             :     }
     130        1414 : }
     131             : 
     132             : /************************************************************************/
     133             : /*                    ParseGeometryColumnCovering()                     */
     134             : /************************************************************************/
     135             : 
     136             : //! Parse bounding box column definition
     137             : /*static */
     138        2706 : bool OGRParquetLayerBase::ParseGeometryColumnCovering(
     139             :     const CPLJSONObject &oJSONDef, std::string &osBBOXColumn,
     140             :     std::string &osXMin, std::string &osYMin, std::string &osXMax,
     141             :     std::string &osYMax)
     142             : {
     143        8118 :     const auto oCovering = oJSONDef["covering"];
     144        4093 :     if (oCovering.IsValid() &&
     145        1387 :         oCovering.GetType() == CPLJSONObject::Type::Object)
     146             :     {
     147        2774 :         const auto oBBOX = oCovering["bbox"];
     148        1387 :         if (oBBOX.IsValid() && oBBOX.GetType() == CPLJSONObject::Type::Object)
     149             :         {
     150        2774 :             const auto oXMin = oBBOX["xmin"];
     151        2774 :             const auto oYMin = oBBOX["ymin"];
     152        2774 :             const auto oXMax = oBBOX["xmax"];
     153        2774 :             const auto oYMax = oBBOX["ymax"];
     154        2774 :             if (oXMin.IsValid() && oYMin.IsValid() && oXMax.IsValid() &&
     155        1387 :                 oYMax.IsValid() &&
     156        1387 :                 oXMin.GetType() == CPLJSONObject::Type::Array &&
     157        1387 :                 oYMin.GetType() == CPLJSONObject::Type::Array &&
     158        4161 :                 oXMax.GetType() == CPLJSONObject::Type::Array &&
     159        1387 :                 oYMax.GetType() == CPLJSONObject::Type::Array)
     160             :             {
     161        1387 :                 const auto osXMinArray = oXMin.ToArray();
     162        1387 :                 const auto osYMinArray = oYMin.ToArray();
     163        1387 :                 const auto osXMaxArray = oXMax.ToArray();
     164        1387 :                 const auto osYMaxArray = oYMax.ToArray();
     165        1387 :                 if (osXMinArray.Size() == 2 && osYMinArray.Size() == 2 &&
     166        1387 :                     osXMaxArray.Size() == 2 && osYMaxArray.Size() == 2 &&
     167        2774 :                     osXMinArray[0].GetType() == CPLJSONObject::Type::String &&
     168        2774 :                     osXMinArray[1].GetType() == CPLJSONObject::Type::String &&
     169        2774 :                     osYMinArray[0].GetType() == CPLJSONObject::Type::String &&
     170        2774 :                     osYMinArray[1].GetType() == CPLJSONObject::Type::String &&
     171        2774 :                     osXMaxArray[0].GetType() == CPLJSONObject::Type::String &&
     172        2774 :                     osXMaxArray[1].GetType() == CPLJSONObject::Type::String &&
     173        2774 :                     osYMaxArray[0].GetType() == CPLJSONObject::Type::String &&
     174        4161 :                     osYMaxArray[1].GetType() == CPLJSONObject::Type::String &&
     175        4161 :                     osXMinArray[0].ToString() == osYMinArray[0].ToString() &&
     176        5548 :                     osXMinArray[0].ToString() == osXMaxArray[0].ToString() &&
     177        2774 :                     osXMinArray[0].ToString() == osYMaxArray[0].ToString())
     178             :                 {
     179        1387 :                     osBBOXColumn = osXMinArray[0].ToString();
     180        1387 :                     osXMin = osXMinArray[1].ToString();
     181        1387 :                     osYMin = osYMinArray[1].ToString();
     182        1387 :                     osXMax = osXMaxArray[1].ToString();
     183        1387 :                     osYMax = osYMaxArray[1].ToString();
     184        1387 :                     return true;
     185             :                 }
     186             :             }
     187             :         }
     188             :     }
     189        1319 :     return false;
     190             : }
     191             : 
     192             : /************************************************************************/
     193             : /*                       DealWithGeometryColumn()                       */
     194             : /************************************************************************/
     195             : 
     196       32690 : bool OGRParquetLayerBase::DealWithGeometryColumn(
     197             :     int iFieldIdx, const std::shared_ptr<arrow::Field> &field,
     198             :     std::function<OGRwkbGeometryType(void)> computeGeometryTypeFun,
     199             :     [[maybe_unused]] const parquet::ColumnDescriptor *parquetColumn,
     200             :     [[maybe_unused]] const parquet::FileMetaData *metadata,
     201             :     [[maybe_unused]] int iColumn)
     202             : {
     203       65380 :     const auto &field_kv_metadata = field->metadata();
     204       65380 :     std::string osExtensionName;
     205       32690 :     if (field_kv_metadata)
     206             :     {
     207         114 :         auto extension_name = field_kv_metadata->Get(ARROW_EXTENSION_NAME_KEY);
     208          57 :         if (extension_name.ok())
     209             :         {
     210           8 :             osExtensionName = *extension_name;
     211             :         }
     212             : #ifdef DEBUG
     213          57 :         CPLDebug("PARQUET", "Metadata field %s:", field->name().c_str());
     214          66 :         for (const auto &keyValue : field_kv_metadata->sorted_pairs())
     215             :         {
     216           9 :             CPLDebug("PARQUET", "  %s = %s", keyValue.first.c_str(),
     217             :                      keyValue.second.c_str());
     218             :         }
     219             : #endif
     220             :     }
     221             : 
     222       32690 :     std::shared_ptr<arrow::DataType> fieldType = field->type();
     223       32690 :     auto fieldTypeId = fieldType->id();
     224       32690 :     if (osExtensionName.empty() && fieldTypeId == arrow::Type::EXTENSION)
     225             :     {
     226             :         auto extensionType =
     227          49 :             cpl::down_cast<arrow::ExtensionType *>(fieldType.get());
     228          49 :         osExtensionName = extensionType->extension_name();
     229             :     }
     230             : 
     231       32690 :     bool bRegularField = true;
     232             : 
     233             : #if PARQUET_VERSION_MAJOR >= 21
     234             :     // Try to detect Arrow >= 21 GEOMETRY/GEOGRAPHY logical type
     235             :     if (fieldTypeId == arrow::Type::EXTENSION)
     236             :     {
     237             :         auto extensionType =
     238             :             cpl::down_cast<arrow::ExtensionType *>(fieldType.get());
     239             :         osExtensionName = extensionType->extension_name();
     240             :         if (osExtensionName == EXTENSION_NAME_GEOARROW_WKB)
     241             :         {
     242             :             const auto arrowWkb =
     243             :                 dynamic_cast<const OGRGeoArrowWkbExtensionType *>(
     244             :                     extensionType);
     245             : #ifdef DEBUG
     246             :             if (arrowWkb)
     247             :             {
     248             :                 CPLDebug("PARQUET", "arrowWkb = '%s'",
     249             :                          arrowWkb->Serialize().c_str());
     250             :             }
     251             : #endif
     252             : 
     253             :             fieldTypeId = extensionType->storage_type()->id();
     254             :             if (fieldTypeId == arrow::Type::BINARY ||
     255             :                 fieldTypeId == arrow::Type::LARGE_BINARY)
     256             :             {
     257             :                 OGRwkbGeometryType eGeomType = wkbUnknown;
     258             :                 bool bSkipRowGroups = false;
     259             : 
     260             :                 // m_aeGeomEncoding be filled before calling
     261             :                 // ComputeGeometryColumnType()
     262             :                 bRegularField = false;
     263             :                 m_aeGeomEncoding.push_back(OGRArrowGeomEncoding::WKB);
     264             : 
     265             :                 std::string crs(m_osCRS);
     266             :                 if (parquetColumn && crs.empty())
     267             :                 {
     268             :                     const auto &logicalType = parquetColumn->logical_type();
     269             :                     if (logicalType->is_geometry())
     270             :                     {
     271             :                         crs = static_cast<const parquet::GeometryLogicalType *>(
     272             :                                   logicalType.get())
     273             :                                   ->crs();
     274             :                         if (crs.empty())
     275             :                             crs = "EPSG:4326";
     276             :                         CPLDebugOnly("PARQUET", "GeometryLogicalType crs=%s",
     277             :                                      crs.c_str());
     278             :                     }
     279             :                     else if (logicalType->is_geography())
     280             :                     {
     281             :                         const auto *geographyType =
     282             :                             static_cast<const parquet::GeographyLogicalType *>(
     283             :                                 logicalType.get());
     284             :                         crs = geographyType->crs();
     285             :                         if (crs.empty())
     286             :                             crs = "EPSG:4326";
     287             : 
     288             :                         SetMetadataItem(
     289             :                             "EDGES",
     290             :                             CPLString(
     291             :                                 std::string(geographyType->algorithm_name()))
     292             :                                 .toupper());
     293             :                         CPLDebugOnly("PARQUET", "GeographyLogicalType crs=%s",
     294             :                                      crs.c_str());
     295             :                     }
     296             :                     else
     297             :                     {
     298             :                         CPLDebug("PARQUET", "geoarrow.wkb column is neither a "
     299             :                                             "geometry or geography one");
     300             : 
     301             :                         // This might be an old geoarrow.wkb extension...
     302             :                         if (CPLTestBool(CPLGetConfigOption(
     303             :                                 "OGR_PARQUET_COMPUTE_GEOMETRY_TYPE", "YES")))
     304             :                         {
     305             :                             eGeomType = computeGeometryTypeFun();
     306             :                             bSkipRowGroups = true;
     307             :                         }
     308             :                     }
     309             : 
     310             :                     // Cf https://github.com/apache/parquet-format/blob/master/Geospatial.md#crs-customization
     311             :                     // "projjson: PROJJSON, identifier is the name of a table property or a file property where the projjson string is stored."
     312             :                     // Here the property is interpreted as the key of a file metadata (as done in libarrow)
     313             :                     constexpr const char *PROJJSON_PREFIX = "projjson:";
     314             :                     if (cpl::starts_with(crs, PROJJSON_PREFIX) && metadata)
     315             :                     {
     316             :                         auto projjson_value =
     317             :                             metadata->key_value_metadata()->Get(
     318             :                                 crs.substr(strlen(PROJJSON_PREFIX)));
     319             :                         if (projjson_value.ok())
     320             :                         {
     321             :                             crs = *projjson_value;
     322             :                         }
     323             :                         else
     324             :                         {
     325             :                             CPLDebug("PARQUET",
     326             :                                      "Cannot find file metadata for %s",
     327             :                                      crs.c_str());
     328             :                         }
     329             :                     }
     330             :                 }
     331             :                 else if (!parquetColumn && arrowWkb)
     332             :                 {
     333             :                     // For a OGRParquetDatasetLayer for example
     334             :                     const std::string arrowWkbMetadata = arrowWkb->Serialize();
     335             :                     if (arrowWkbMetadata.empty() || arrowWkbMetadata == "{}")
     336             :                     {
     337             :                         crs = "EPSG:4326";
     338             :                     }
     339             :                     else if (arrowWkbMetadata[0] == '{')
     340             :                     {
     341             :                         CPLJSONDocument oDoc;
     342             :                         if (oDoc.LoadMemory(arrowWkbMetadata))
     343             :                         {
     344             :                             auto jCrs = oDoc.GetRoot()["crs"];
     345             :                             if (jCrs.GetType() == CPLJSONObject::Type::Object)
     346             :                             {
     347             :                                 crs = jCrs.Format(
     348             :                                     CPLJSONObject::PrettyFormat::Plain);
     349             :                             }
     350             :                             else if (jCrs.GetType() ==
     351             :                                      CPLJSONObject::Type::String)
     352             :                             {
     353             :                                 crs = jCrs.ToString();
     354             :                             }
     355             :                             if (oDoc.GetRoot()["edges"].ToString() ==
     356             :                                 "spherical")
     357             :                             {
     358             :                                 SetMetadataItem("EDGES", "SPHERICAL");
     359             :                             }
     360             :                         }
     361             :                     }
     362             :                 }
     363             : 
     364             :                 bool bGeomTypeInvalid = false;
     365             :                 bool bHasMulti = false;
     366             :                 bool bHasZ = false;
     367             :                 bool bHasM = false;
     368             :                 bool bFirst = true;
     369             :                 OGRwkbGeometryType eFirstType = wkbUnknown;
     370             :                 OGRwkbGeometryType eFirstTypeCollection = wkbUnknown;
     371             :                 const auto numRowGroups =
     372             :                     metadata ? metadata->num_row_groups() : 0;
     373             :                 bool bEnvelopeValid = true;
     374             :                 OGREnvelope sEnvelope;
     375             :                 bool bEnvelope3DValid = true;
     376             :                 OGREnvelope3D sEnvelope3D;
     377             :                 for (int iRowGroup = 0;
     378             :                      !bSkipRowGroups && iRowGroup < numRowGroups; ++iRowGroup)
     379             :                 {
     380             :                     const auto columnChunk =
     381             :                         metadata->RowGroup(iRowGroup)->ColumnChunk(iColumn);
     382             :                     if (auto geostats = columnChunk->geo_statistics())
     383             :                     {
     384             :                         double dfMinX =
     385             :                             std::numeric_limits<double>::quiet_NaN();
     386             :                         double dfMinY =
     387             :                             std::numeric_limits<double>::quiet_NaN();
     388             :                         double dfMinZ =
     389             :                             std::numeric_limits<double>::quiet_NaN();
     390             :                         double dfMaxX =
     391             :                             std::numeric_limits<double>::quiet_NaN();
     392             :                         double dfMaxY =
     393             :                             std::numeric_limits<double>::quiet_NaN();
     394             :                         double dfMaxZ =
     395             :                             std::numeric_limits<double>::quiet_NaN();
     396             :                         if (bEnvelopeValid && geostats->dimension_valid()[0] &&
     397             :                             geostats->dimension_valid()[1])
     398             :                         {
     399             :                             dfMinX = geostats->lower_bound()[0];
     400             :                             dfMaxX = geostats->upper_bound()[0];
     401             :                             dfMinY = geostats->lower_bound()[1];
     402             :                             dfMaxY = geostats->upper_bound()[1];
     403             : 
     404             :                             // Deal as best as we can with wrap around bounding box
     405             :                             if (dfMinX > dfMaxX && std::fabs(dfMinX) <= 180 &&
     406             :                                 std::fabs(dfMaxX) <= 180)
     407             :                             {
     408             :                                 dfMinX = -180;
     409             :                                 dfMaxX = 180;
     410             :                             }
     411             : 
     412             :                             if (std::isfinite(dfMinX) &&
     413             :                                 std::isfinite(dfMaxX) &&
     414             :                                 std::isfinite(dfMinY) && std::isfinite(dfMaxY))
     415             :                             {
     416             :                                 sEnvelope.Merge(dfMinX, dfMinY);
     417             :                                 sEnvelope.Merge(dfMaxX, dfMaxY);
     418             :                                 if (bEnvelope3DValid &&
     419             :                                     geostats->dimension_valid()[2])
     420             :                                 {
     421             :                                     dfMinZ = geostats->lower_bound()[2];
     422             :                                     dfMaxZ = geostats->upper_bound()[2];
     423             :                                     if (std::isfinite(dfMinZ) &&
     424             :                                         std::isfinite(dfMaxZ))
     425             :                                     {
     426             :                                         sEnvelope3D.Merge(dfMinX, dfMinY,
     427             :                                                           dfMinZ);
     428             :                                         sEnvelope3D.Merge(dfMaxX, dfMaxY,
     429             :                                                           dfMaxZ);
     430             :                                     }
     431             :                                 }
     432             :                             }
     433             :                         }
     434             : 
     435             :                         bEnvelopeValid =
     436             :                             bEnvelopeValid && std::isfinite(dfMinX) &&
     437             :                             std::isfinite(dfMaxX) && std::isfinite(dfMinY) &&
     438             :                             std::isfinite(dfMaxY);
     439             : 
     440             :                         bEnvelope3DValid = bEnvelope3DValid &&
     441             :                                            std::isfinite(dfMinZ) &&
     442             :                                            std::isfinite(dfMaxZ);
     443             : 
     444             :                         if (auto geometry_types = geostats->geometry_types())
     445             :                         {
     446             :                             const auto PromoteToCollection =
     447             :                                 [](OGRwkbGeometryType eType)
     448             :                             {
     449             :                                 if (eType == wkbPoint)
     450             :                                     return wkbMultiPoint;
     451             :                                 if (eType == wkbLineString)
     452             :                                     return wkbMultiLineString;
     453             :                                 if (eType == wkbPolygon)
     454             :                                     return wkbMultiPolygon;
     455             :                                 return eType;
     456             :                             };
     457             : 
     458             :                             for (int nGeomType : *geometry_types)
     459             :                             {
     460             :                                 OGRwkbGeometryType eThisGeom = wkbUnknown;
     461             :                                 if ((nGeomType > 0 && nGeomType <= 17) ||
     462             :                                     (nGeomType > 2000 && nGeomType <= 2017) ||
     463             :                                     (nGeomType > 3000 && nGeomType <= 3017))
     464             :                                 {
     465             :                                     eThisGeom = static_cast<OGRwkbGeometryType>(
     466             :                                         nGeomType);
     467             :                                 }
     468             :                                 else if (nGeomType > 1000 && nGeomType <= 1017)
     469             :                                 {
     470             :                                     eThisGeom = OGR_GT_SetZ(
     471             :                                         static_cast<OGRwkbGeometryType>(
     472             :                                             nGeomType - 1000));
     473             :                                     ;
     474             :                                 }
     475             :                                 else
     476             :                                 {
     477             :                                     CPLDebug("PARQUET",
     478             :                                              "Unknown geometry type: %d",
     479             :                                              nGeomType);
     480             :                                     bGeomTypeInvalid = true;
     481             :                                     break;
     482             :                                 }
     483             :                                 if (bFirst)
     484             :                                 {
     485             :                                     bFirst = false;
     486             :                                     eFirstType = eThisGeom;
     487             :                                     eFirstTypeCollection =
     488             :                                         PromoteToCollection(eFirstType);
     489             :                                 }
     490             :                                 else if (PromoteToCollection(
     491             :                                              OGR_GT_Flatten(eThisGeom)) !=
     492             :                                          eFirstTypeCollection)
     493             :                                 {
     494             :                                     bGeomTypeInvalid = true;
     495             :                                     break;
     496             :                                 }
     497             :                                 bHasZ |= OGR_GT_HasZ(eThisGeom) != FALSE;
     498             :                                 bHasM |= OGR_GT_HasM(eThisGeom) != FALSE;
     499             :                                 bHasMulti |= (PromoteToCollection(
     500             :                                                   OGR_GT_Flatten(eThisGeom)) ==
     501             :                                               OGR_GT_Flatten(eThisGeom));
     502             :                             }
     503             :                         }
     504             :                     }
     505             :                     else
     506             :                     {
     507             :                         bEnvelopeValid = false;
     508             :                         bEnvelope3DValid = false;
     509             :                         bGeomTypeInvalid = true;
     510             :                     }
     511             :                 }
     512             : 
     513             :                 if (bEnvelopeValid && sEnvelope.IsInit())
     514             :                 {
     515             :                     CPLDebug("PARQUET", "Got bounding box from geo_statistics");
     516             :                     m_geoStatsWithBBOXAvailable.insert(
     517             :                         m_poFeatureDefn->GetGeomFieldCount());
     518             :                     m_oMapExtents[m_poFeatureDefn->GetGeomFieldCount()] =
     519             :                         std::move(sEnvelope);
     520             : 
     521             :                     if (bEnvelope3DValid && sEnvelope3D.IsInit())
     522             :                     {
     523             :                         CPLDebug("PARQUET",
     524             :                                  "Got bounding box 3D from geo_statistics");
     525             :                         m_oMapExtents3D[m_poFeatureDefn->GetGeomFieldCount()] =
     526             :                             std::move(sEnvelope3D);
     527             :                     }
     528             :                 }
     529             : 
     530             :                 if (!bSkipRowGroups && !bGeomTypeInvalid)
     531             :                 {
     532             :                     if (eFirstTypeCollection == wkbMultiPoint ||
     533             :                         eFirstTypeCollection == wkbMultiPolygon ||
     534             :                         eFirstTypeCollection == wkbMultiLineString)
     535             :                     {
     536             :                         if (bHasMulti)
     537             :                             eGeomType = OGR_GT_SetModifier(eFirstTypeCollection,
     538             :                                                            bHasZ, bHasM);
     539             :                         else
     540             :                             eGeomType =
     541             :                                 OGR_GT_SetModifier(eFirstType, bHasZ, bHasM);
     542             :                     }
     543             :                 }
     544             : 
     545             :                 OGRGeomFieldDefn oField(field->name().c_str(), eGeomType);
     546             :                 oField.SetNullable(field->nullable());
     547             : 
     548             :                 if (!crs.empty())
     549             :                 {
     550             :                     // Cf https://github.com/apache/parquet-format/blob/master/Geospatial.md#crs-customization
     551             :                     // "srid: Spatial reference identifier, identifier is the SRID itself.."
     552             :                     constexpr const char *SRID_PREFIX = "srid:";
     553             :                     if (cpl::starts_with(crs, SRID_PREFIX))
     554             :                     {
     555             :                         // When getting the value from the GeometryLogicalType::crs() method
     556             :                         crs = crs.substr(strlen(SRID_PREFIX));
     557             :                     }
     558             :                     if (CPLGetValueType(crs.c_str()) == CPL_VALUE_INTEGER)
     559             :                     {
     560             :                         // Getting here from above if, or if reading the ArrowWkb
     561             :                         // metadata directly (typically from a OGRParquetDatasetLayer)
     562             : 
     563             :                         // Assumes a SRID code is an EPSG code...
     564             :                         crs = std::string("EPSG:") + crs;
     565             :                     }
     566             : 
     567             :                     auto poSRS = new OGRSpatialReference();
     568             :                     poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     569             :                     if (poSRS->SetFromUserInput(
     570             :                             crs.c_str(),
     571             :                             OGRSpatialReference::
     572             :                                 SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
     573             :                         OGRERR_NONE)
     574             :                     {
     575             :                         const char *pszAuthName =
     576             :                             poSRS->GetAuthorityName(nullptr);
     577             :                         const char *pszAuthCode =
     578             :                             poSRS->GetAuthorityCode(nullptr);
     579             :                         if (pszAuthName && pszAuthCode &&
     580             :                             EQUAL(pszAuthName, "OGC") &&
     581             :                             EQUAL(pszAuthCode, "CRS84"))
     582             :                             poSRS->importFromEPSG(4326);
     583             :                         oField.SetSpatialRef(poSRS);
     584             :                     }
     585             :                     poSRS->Release();
     586             :                 }
     587             : 
     588             :                 m_poFeatureDefn->AddGeomFieldDefn(&oField);
     589             :                 m_anMapGeomFieldIndexToArrowColumn.push_back(iFieldIdx);
     590             :             }
     591             :         }
     592             :     }
     593             : #endif
     594             : 
     595       32690 :     auto oIter = m_oMapGeometryColumns.find(field->name());
     596             :     // cppcheck-suppress knownConditionTrueFalse
     597       64022 :     if (bRegularField && (oIter != m_oMapGeometryColumns.end() ||
     598       31332 :                           STARTS_WITH(osExtensionName.c_str(), "ogc.") ||
     599       31332 :                           STARTS_WITH(osExtensionName.c_str(), "geoarrow.")))
     600             :     {
     601        2720 :         CPLJSONObject oJSONDef;
     602        1360 :         if (oIter != m_oMapGeometryColumns.end())
     603        1358 :             oJSONDef = oIter->second;
     604        4080 :         auto osEncoding = oJSONDef.GetString("encoding");
     605        1360 :         if (osEncoding.empty() && !osExtensionName.empty())
     606           2 :             osEncoding = osExtensionName;
     607             : 
     608        1360 :         OGRwkbGeometryType eGeomType = wkbUnknown;
     609        1360 :         auto eGeomEncoding = OGRArrowGeomEncoding::WKB;
     610        1360 :         if (IsValidGeometryEncoding(field, osEncoding,
     611        2720 :                                     oIter != m_oMapGeometryColumns.end(),
     612             :                                     eGeomType, eGeomEncoding))
     613             :         {
     614        1360 :             bRegularField = false;
     615        2720 :             OGRGeomFieldDefn oField(field->name().c_str(), wkbUnknown);
     616             : 
     617        4080 :             auto oCRS = oJSONDef["crs"];
     618        1360 :             OGRSpatialReference *poSRS = nullptr;
     619        1360 :             if (!oCRS.IsValid())
     620             :             {
     621          49 :                 if (!m_oMapGeometryColumns.empty())
     622             :                 {
     623             :                     // WGS 84 is implied if no crs member is found.
     624          47 :                     poSRS = new OGRSpatialReference();
     625          47 :                     poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     626          47 :                     poSRS->importFromEPSG(4326);
     627             :                 }
     628             :             }
     629        1311 :             else if (oCRS.GetType() == CPLJSONObject::Type::String)
     630             :             {
     631        1119 :                 const auto osWKT = oCRS.ToString();
     632         373 :                 poSRS = new OGRSpatialReference();
     633         373 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     634             : 
     635         373 :                 if (poSRS->importFromWkt(osWKT.c_str()) != OGRERR_NONE)
     636             :                 {
     637           0 :                     poSRS->Release();
     638           0 :                     poSRS = nullptr;
     639             :                 }
     640             :             }
     641         938 :             else if (oCRS.GetType() == CPLJSONObject::Type::Object)
     642             :             {
     643             :                 // CRS encoded as PROJJSON (extension)
     644         120 :                 const auto oType = oCRS["type"];
     645          80 :                 if (oType.IsValid() &&
     646          40 :                     oType.GetType() == CPLJSONObject::Type::String)
     647             :                 {
     648         120 :                     const auto osType = oType.ToString();
     649          40 :                     if (osType.find("CRS") != std::string::npos)
     650             :                     {
     651          40 :                         poSRS = new OGRSpatialReference();
     652          40 :                         poSRS->SetAxisMappingStrategy(
     653             :                             OAMS_TRADITIONAL_GIS_ORDER);
     654             : 
     655          80 :                         if (poSRS->SetFromUserInput(
     656          80 :                                 oCRS.ToString().c_str(),
     657             :                                 OGRSpatialReference::
     658          40 :                                     SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
     659             :                             OGRERR_NONE)
     660             :                         {
     661           0 :                             poSRS->Release();
     662           0 :                             poSRS = nullptr;
     663             :                         }
     664             :                     }
     665             :                 }
     666             :             }
     667             : 
     668        1360 :             if (poSRS)
     669             :             {
     670         460 :                 const double dfCoordEpoch = oJSONDef.GetDouble("epoch");
     671         460 :                 if (dfCoordEpoch > 0)
     672           4 :                     poSRS->SetCoordinateEpoch(dfCoordEpoch);
     673             : 
     674         460 :                 oField.SetSpatialRef(poSRS);
     675             : 
     676         460 :                 poSRS->Release();
     677             :             }
     678             : 
     679        1360 :             if (!m_osCRS.empty())
     680             :             {
     681           0 :                 poSRS = new OGRSpatialReference();
     682           0 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     683           0 :                 if (poSRS->SetFromUserInput(
     684             :                         m_osCRS.c_str(),
     685             :                         OGRSpatialReference::
     686           0 :                             SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
     687             :                     OGRERR_NONE)
     688             :                 {
     689           0 :                     oField.SetSpatialRef(poSRS);
     690             :                 }
     691           0 :                 poSRS->Release();
     692             :             }
     693             : 
     694        1360 :             if (oJSONDef.GetString("edges") == "spherical")
     695             :             {
     696           5 :                 SetMetadataItem("EDGES", "SPHERICAL");
     697             :             }
     698             : 
     699             :             // m_aeGeomEncoding be filled before calling
     700             :             // ComputeGeometryColumnType()
     701        1360 :             m_aeGeomEncoding.push_back(eGeomEncoding);
     702        1360 :             if (eGeomType == wkbUnknown)
     703             :             {
     704             :                 // geometry_types since 1.0.0-beta1. Was geometry_type
     705             :                 // before
     706        1809 :                 auto oType = oJSONDef.GetObj("geometry_types");
     707         603 :                 if (!oType.IsValid())
     708         377 :                     oType = oJSONDef.GetObj("geometry_type");
     709         603 :                 if (oType.GetType() == CPLJSONObject::Type::String)
     710             :                 {
     711             :                     // string is no longer valid since 1.0.0-beta1
     712           3 :                     const auto osType = oType.ToString();
     713           1 :                     if (osType != "Unknown")
     714           1 :                         eGeomType = GetGeometryTypeFromString(osType);
     715             :                 }
     716         602 :                 else if (oType.GetType() == CPLJSONObject::Type::Array)
     717             :                 {
     718         452 :                     const auto oTypeArray = oType.ToArray();
     719         226 :                     if (oTypeArray.Size() == 1)
     720             :                     {
     721         113 :                         eGeomType =
     722         113 :                             GetGeometryTypeFromString(oTypeArray[0].ToString());
     723             :                     }
     724         113 :                     else if (oTypeArray.Size() > 1)
     725             :                     {
     726             :                         const auto PromoteToCollection =
     727         266 :                             [](OGRwkbGeometryType eType)
     728             :                         {
     729         266 :                             if (eType == wkbPoint)
     730          41 :                                 return wkbMultiPoint;
     731         225 :                             if (eType == wkbLineString)
     732          36 :                                 return wkbMultiLineString;
     733         189 :                             if (eType == wkbPolygon)
     734          49 :                                 return wkbMultiPolygon;
     735         140 :                             return eType;
     736             :                         };
     737          50 :                         bool bMixed = false;
     738          50 :                         bool bHasMulti = false;
     739          50 :                         bool bHasZ = false;
     740          50 :                         bool bHasM = false;
     741             :                         const auto eFirstType =
     742          50 :                             OGR_GT_Flatten(GetGeometryTypeFromString(
     743         100 :                                 oTypeArray[0].ToString()));
     744             :                         const auto eFirstTypeCollection =
     745          50 :                             PromoteToCollection(eFirstType);
     746         142 :                         for (int i = 0; i < oTypeArray.Size(); ++i)
     747             :                         {
     748         124 :                             const auto eThisGeom = GetGeometryTypeFromString(
     749         248 :                                 oTypeArray[i].ToString());
     750         124 :                             if (PromoteToCollection(OGR_GT_Flatten(
     751         124 :                                     eThisGeom)) != eFirstTypeCollection)
     752             :                             {
     753          32 :                                 bMixed = true;
     754          32 :                                 break;
     755             :                             }
     756          92 :                             bHasZ |= OGR_GT_HasZ(eThisGeom) != FALSE;
     757          92 :                             bHasM |= OGR_GT_HasM(eThisGeom) != FALSE;
     758          92 :                             bHasMulti |=
     759          92 :                                 (PromoteToCollection(OGR_GT_Flatten(
     760          92 :                                      eThisGeom)) == OGR_GT_Flatten(eThisGeom));
     761             :                         }
     762          50 :                         if (!bMixed)
     763             :                         {
     764          18 :                             if (eFirstTypeCollection == wkbMultiPolygon ||
     765             :                                 eFirstTypeCollection == wkbMultiLineString)
     766             :                             {
     767          17 :                                 if (bHasMulti)
     768          17 :                                     eGeomType = OGR_GT_SetModifier(
     769             :                                         eFirstTypeCollection, bHasZ, bHasM);
     770             :                                 else
     771           0 :                                     eGeomType = OGR_GT_SetModifier(
     772             :                                         eFirstType, bHasZ, bHasM);
     773             :                             }
     774             :                         }
     775             :                     }
     776             :                 }
     777         376 :                 else if (CPLTestBool(CPLGetConfigOption(
     778             :                              "OGR_PARQUET_COMPUTE_GEOMETRY_TYPE", "YES")))
     779             :                 {
     780         376 :                     eGeomType = computeGeometryTypeFun();
     781             :                 }
     782             :             }
     783             : 
     784        1360 :             oField.SetType(eGeomType);
     785        1360 :             oField.SetNullable(field->nullable());
     786        1360 :             m_poFeatureDefn->AddGeomFieldDefn(&oField);
     787        1360 :             m_anMapGeomFieldIndexToArrowColumn.push_back(iFieldIdx);
     788             :         }
     789             :     }
     790             : 
     791             :     // Try to autodetect a (WKB) geometry column from the GEOM_POSSIBLE_NAMES
     792             :     // open option
     793       62605 :     if (bRegularField && osExtensionName.empty() &&
     794       95295 :         m_oMapGeometryColumns.empty() &&
     795         269 :         m_aosGeomPossibleNames.FindString(field->name().c_str()) >= 0)
     796             :     {
     797          13 :         if (fieldTypeId == arrow::Type::BINARY ||
     798             :             fieldTypeId == arrow::Type::LARGE_BINARY)
     799             :         {
     800           7 :             CPLDebug("PARQUET",
     801             :                      "Field %s detected as likely WKB geometry field",
     802           7 :                      field->name().c_str());
     803           7 :             bRegularField = false;
     804           7 :             m_aeGeomEncoding.push_back(OGRArrowGeomEncoding::WKB);
     805             :         }
     806           0 :         else if ((fieldTypeId == arrow::Type::STRING ||
     807          16 :                   fieldTypeId == arrow::Type::LARGE_STRING) &&
     808          10 :                  (field->name().find("wkt") != std::string::npos ||
     809           4 :                   field->name().find("WKT") != std::string::npos))
     810             :         {
     811           2 :             CPLDebug("PARQUET",
     812             :                      "Field %s detected as likely WKT geometry field",
     813           2 :                      field->name().c_str());
     814           2 :             bRegularField = false;
     815           2 :             m_aeGeomEncoding.push_back(OGRArrowGeomEncoding::WKT);
     816             :         }
     817          13 :         if (!bRegularField)
     818             :         {
     819          18 :             OGRGeomFieldDefn oField(field->name().c_str(), wkbUnknown);
     820           9 :             oField.SetNullable(field->nullable());
     821             : 
     822           9 :             if (!m_osCRS.empty())
     823             :             {
     824           2 :                 auto poSRS = new OGRSpatialReference();
     825           2 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     826           2 :                 if (poSRS->SetFromUserInput(
     827             :                         m_osCRS.c_str(),
     828             :                         OGRSpatialReference::
     829           2 :                             SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
     830             :                     OGRERR_NONE)
     831             :                 {
     832           2 :                     oField.SetSpatialRef(poSRS);
     833             :                 }
     834           2 :                 poSRS->Release();
     835             :             }
     836             : 
     837           9 :             m_poFeatureDefn->AddGeomFieldDefn(&oField);
     838           9 :             m_anMapGeomFieldIndexToArrowColumn.push_back(iFieldIdx);
     839             :         }
     840             :     }
     841             : 
     842       65380 :     return !bRegularField;
     843             : }
     844             : 
     845             : /************************************************************************/
     846             : /*                           TestCapability()                           */
     847             : /************************************************************************/
     848             : 
     849         768 : int OGRParquetLayerBase::TestCapability(const char *pszCap) const
     850             : {
     851         768 :     if (EQUAL(pszCap, OLCMeasuredGeometries))
     852          32 :         return true;
     853             : 
     854         736 :     if (EQUAL(pszCap, OLCFastSetNextByIndex))
     855           0 :         return true;
     856             : 
     857         736 :     if (EQUAL(pszCap, OLCFastSpatialFilter))
     858             :     {
     859          73 :         if (m_oMapGeomFieldIndexToGeomColBBOX.find(m_iGeomFieldFilter) !=
     860         146 :             m_oMapGeomFieldIndexToGeomColBBOX.end())
     861             :         {
     862          49 :             return true;
     863             :         }
     864          24 :         return false;
     865             :     }
     866             : 
     867         663 :     return OGRArrowLayer::TestCapability(pszCap);
     868             : }
     869             : 
     870             : /************************************************************************/
     871             : /*                             GetNumCPUs()                             */
     872             : /************************************************************************/
     873             : 
     874             : /* static */
     875        1996 : int OGRParquetLayerBase::GetNumCPUs()
     876             : {
     877        1996 :     const char *pszNumThreads = nullptr;
     878             :     int nNumThreads =
     879        1996 :         GDALGetNumThreads(pszNumThreads,
     880             :                           /* nMaxVal = */ -1,
     881             :                           /* bDefaultToAllCPUs = */ false, &pszNumThreads);
     882        1996 :     if (pszNumThreads == nullptr)
     883           0 :         nNumThreads = std::min(4, CPLGetNumCPUs());
     884        1996 :     if (nNumThreads > 1)
     885             :     {
     886           0 :         CPL_IGNORE_RET_VAL(arrow::SetCpuThreadPoolCapacity(nNumThreads));
     887             :     }
     888        1996 :     return nNumThreads;
     889             : }
     890             : 
     891             : /************************************************************************/
     892             : /*                          OGRParquetLayer()                           */
     893             : /************************************************************************/
     894             : 
     895        1054 : OGRParquetLayer::OGRParquetLayer(
     896             :     OGRParquetDataset *poDS, const char *pszLayerName,
     897             :     std::unique_ptr<parquet::arrow::FileReader> &&arrow_reader,
     898        1054 :     CSLConstList papszOpenOptions)
     899             :     : OGRParquetLayerBase(poDS, pszLayerName, papszOpenOptions),
     900        1054 :       m_poArrowReader(std::move(arrow_reader))
     901             : {
     902        1054 :     EstablishFeatureDefn();
     903        1054 :     CPLAssert(static_cast<int>(m_aeGeomEncoding.size()) ==
     904             :               m_poFeatureDefn->GetGeomFieldCount());
     905             : 
     906        1054 :     m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
     907        1054 : }
     908             : 
     909             : /************************************************************************/
     910             : /*                        EstablishFeatureDefn()                        */
     911             : /************************************************************************/
     912             : 
     913        1054 : void OGRParquetLayer::EstablishFeatureDefn()
     914             : {
     915        1054 :     const auto metadata = m_poArrowReader->parquet_reader()->metadata();
     916        1054 :     const auto &kv_metadata = metadata->key_value_metadata();
     917             : 
     918        1054 :     LoadGeoMetadata(kv_metadata);
     919             :     const auto oMapFieldNameToGDALSchemaFieldDefn =
     920        1054 :         LoadGDALSchema(kv_metadata.get());
     921             : 
     922        1054 :     LoadGDALMetadata(kv_metadata.get());
     923             : 
     924        1054 :     if (kv_metadata && kv_metadata->Contains("gdal:creation-options"))
     925             :     {
     926        1110 :         auto co = kv_metadata->Get("gdal:creation-options");
     927         555 :         if (co.ok())
     928             :         {
     929         555 :             CPLDebugOnly("PARQUET", "gdal:creation-options = %s", co->c_str());
     930        1110 :             CPLJSONDocument oDoc;
     931         555 :             if (oDoc.LoadMemory(*co))
     932             :             {
     933        1110 :                 auto oRoot = oDoc.GetRoot();
     934         555 :                 if (oRoot.GetType() == CPLJSONObject::Type::Object)
     935             :                 {
     936        1786 :                     for (const auto &oChild : oRoot.GetChildren())
     937             :                     {
     938        1231 :                         if (oChild.GetType() == CPLJSONObject::Type::String)
     939             :                         {
     940             :                             m_aosCreationOptions.SetNameValue(
     941        2462 :                                 oChild.GetName().c_str(),
     942        3693 :                                 oChild.ToString().c_str());
     943             :                         }
     944             :                     }
     945             :                 }
     946             :             }
     947             :         }
     948             :     }
     949             : 
     950        1054 :     if (!m_poArrowReader->GetSchema(&m_poSchema).ok())
     951             :     {
     952           0 :         return;
     953             :     }
     954             : 
     955             :     const bool bUseBBOX =
     956        1054 :         CPLTestBool(CPLGetConfigOption("OGR_PARQUET_USE_BBOX", "YES"));
     957             : 
     958             :     // Keep track of declared bounding box columns in GeoParquet JSON metadata,
     959             :     // in order not to expose them as regular fields.
     960        2108 :     std::set<std::string> oSetBBOXColumns;
     961        1054 :     if (bUseBBOX)
     962             :     {
     963        2065 :         for (const auto &iter : m_oMapGeometryColumns)
     964             :         {
     965        2032 :             std::string osBBOXColumn;
     966        2032 :             std::string osXMin, osYMin, osXMax, osYMax;
     967        1016 :             if (ParseGeometryColumnCovering(iter.second, osBBOXColumn, osXMin,
     968             :                                             osYMin, osXMax, osYMax))
     969             :             {
     970         520 :                 oSetBBOXColumns.insert(std::move(osBBOXColumn));
     971             :             }
     972             :         }
     973             :     }
     974             : 
     975        1054 :     const auto &fields = m_poSchema->fields();
     976        1054 :     const auto poParquetSchema = metadata->schema();
     977             : 
     978             :     // Map from Parquet column name (with dot separator) to Parquet index
     979        2108 :     std::map<std::string, int> oMapParquetColumnNameToIdx;
     980        1054 :     const int nParquetColumns = poParquetSchema->num_columns();
     981       37163 :     for (int iParquetCol = 0; iParquetCol < nParquetColumns; ++iParquetCol)
     982             :     {
     983       36109 :         const auto parquetColumn = poParquetSchema->Column(iParquetCol);
     984       36109 :         const auto parquetColumnName = parquetColumn->path()->ToDotString();
     985       36109 :         oMapParquetColumnNameToIdx[parquetColumnName] = iParquetCol;
     986             :     }
     987             : 
     988             :     // Synthetize a GeoParquet bounding box column definition when detecting
     989             :     // a Overture Map dataset < 2024-04-16-beta.0
     990        1003 :     if ((m_oMapGeometryColumns.empty() ||
     991             :          // Below is for release 2024-01-17-alpha.0
     992        2057 :          (m_oMapGeometryColumns.find("geometry") !=
     993        2057 :               m_oMapGeometryColumns.end() &&
     994        2540 :           !m_oMapGeometryColumns["geometry"].GetObj("covering").IsValid() &&
     995        1913 :           m_oMapGeometryColumns["geometry"].GetString("encoding") == "WKB")) &&
     996         361 :         bUseBBOX &&
     997        1415 :         oMapParquetColumnNameToIdx.find("geometry") !=
     998        1734 :             oMapParquetColumnNameToIdx.end() &&
     999        1373 :         oMapParquetColumnNameToIdx.find("bbox.minx") !=
    1000        1374 :             oMapParquetColumnNameToIdx.end() &&
    1001        1055 :         oMapParquetColumnNameToIdx.find("bbox.miny") !=
    1002        1056 :             oMapParquetColumnNameToIdx.end() &&
    1003        1055 :         oMapParquetColumnNameToIdx.find("bbox.maxx") !=
    1004        3163 :             oMapParquetColumnNameToIdx.end() &&
    1005        1055 :         oMapParquetColumnNameToIdx.find("bbox.maxy") !=
    1006        1055 :             oMapParquetColumnNameToIdx.end())
    1007             :     {
    1008           2 :         CPLJSONObject oDef;
    1009           1 :         if (m_oMapGeometryColumns.find("geometry") !=
    1010           2 :             m_oMapGeometryColumns.end())
    1011             :         {
    1012           0 :             oDef = m_oMapGeometryColumns["geometry"];
    1013             :         }
    1014           2 :         CPLJSONObject oCovering;
    1015           1 :         oDef.Add("covering", oCovering);
    1016           1 :         CPLJSONObject oBBOX;
    1017           1 :         oCovering.Add("bbox", oBBOX);
    1018             :         {
    1019           1 :             CPLJSONArray oArray;
    1020           1 :             oArray.Add("bbox");
    1021           1 :             oArray.Add("minx");
    1022           1 :             oBBOX.Add("xmin", oArray);
    1023             :         }
    1024             :         {
    1025           1 :             CPLJSONArray oArray;
    1026           1 :             oArray.Add("bbox");
    1027           1 :             oArray.Add("miny");
    1028           1 :             oBBOX.Add("ymin", oArray);
    1029             :         }
    1030             :         {
    1031           1 :             CPLJSONArray oArray;
    1032           1 :             oArray.Add("bbox");
    1033           1 :             oArray.Add("maxx");
    1034           1 :             oBBOX.Add("xmax", oArray);
    1035             :         }
    1036             :         {
    1037           1 :             CPLJSONArray oArray;
    1038           1 :             oArray.Add("bbox");
    1039           1 :             oArray.Add("maxy");
    1040           1 :             oBBOX.Add("ymax", oArray);
    1041             :         }
    1042           1 :         oSetBBOXColumns.insert("bbox");
    1043           1 :         oDef.Add("encoding", "WKB");
    1044           1 :         m_oMapGeometryColumns["geometry"] = std::move(oDef);
    1045             :     }
    1046             :     // Overture Maps 2024-04-16-beta.0 almost follows GeoParquet 1.1, except
    1047             :     // they don't declare the "covering" element in the GeoParquet JSON metadata
    1048        2106 :     else if (m_oMapGeometryColumns.find("geometry") !=
    1049        2043 :                  m_oMapGeometryColumns.end() &&
    1050        1972 :              bUseBBOX &&
    1051        2534 :              !m_oMapGeometryColumns["geometry"].GetObj("covering").IsValid() &&
    1052        1859 :              m_oMapGeometryColumns["geometry"].GetString("encoding") == "WKB" &&
    1053        1364 :              oMapParquetColumnNameToIdx.find("geometry") !=
    1054        1675 :                  oMapParquetColumnNameToIdx.end() &&
    1055        1364 :              oMapParquetColumnNameToIdx.find("bbox.xmin") !=
    1056        1365 :                  oMapParquetColumnNameToIdx.end() &&
    1057        1054 :              oMapParquetColumnNameToIdx.find("bbox.ymin") !=
    1058        1055 :                  oMapParquetColumnNameToIdx.end() &&
    1059        1054 :              oMapParquetColumnNameToIdx.find("bbox.xmax") !=
    1060        4150 :                  oMapParquetColumnNameToIdx.end() &&
    1061        1054 :              oMapParquetColumnNameToIdx.find("bbox.ymax") !=
    1062        1054 :                  oMapParquetColumnNameToIdx.end())
    1063             :     {
    1064           3 :         CPLJSONObject oDef = m_oMapGeometryColumns["geometry"];
    1065           2 :         CPLJSONObject oCovering;
    1066           1 :         oDef.Add("covering", oCovering);
    1067           1 :         CPLJSONObject oBBOX;
    1068           1 :         oCovering.Add("bbox", oBBOX);
    1069             :         {
    1070           1 :             CPLJSONArray oArray;
    1071           1 :             oArray.Add("bbox");
    1072           1 :             oArray.Add("xmin");
    1073           1 :             oBBOX.Add("xmin", oArray);
    1074             :         }
    1075             :         {
    1076           1 :             CPLJSONArray oArray;
    1077           1 :             oArray.Add("bbox");
    1078           1 :             oArray.Add("ymin");
    1079           1 :             oBBOX.Add("ymin", oArray);
    1080             :         }
    1081             :         {
    1082           1 :             CPLJSONArray oArray;
    1083           1 :             oArray.Add("bbox");
    1084           1 :             oArray.Add("xmax");
    1085           1 :             oBBOX.Add("xmax", oArray);
    1086             :         }
    1087             :         {
    1088           1 :             CPLJSONArray oArray;
    1089           1 :             oArray.Add("bbox");
    1090           1 :             oArray.Add("ymax");
    1091           1 :             oBBOX.Add("ymax", oArray);
    1092             :         }
    1093           1 :         oSetBBOXColumns.insert("bbox");
    1094           1 :         m_oMapGeometryColumns["geometry"] = std::move(oDef);
    1095             :     }
    1096             : 
    1097        1054 :     int iParquetCol = 0;
    1098       27313 :     for (int i = 0; i < m_poSchema->num_fields(); ++i)
    1099             :     {
    1100       26259 :         const auto &field = fields[i];
    1101             : 
    1102             :         bool bParquetColValid =
    1103       26259 :             CheckMatchArrowParquetColumnNames(iParquetCol, field);
    1104       26259 :         if (!bParquetColValid)
    1105           0 :             m_bHasMissingMappingToParquet = true;
    1106             : 
    1107       26303 :         if (!m_osFIDColumn.empty() && field->name() == m_osFIDColumn &&
    1108          44 :             (field->type()->id() == arrow::Type::INT32 ||
    1109          22 :              field->type()->id() == arrow::Type::INT64))
    1110             :         {
    1111          22 :             m_poFIDType = field->type();
    1112          22 :             m_iFIDArrowColumn = i;
    1113          22 :             if (bParquetColValid)
    1114             :             {
    1115          22 :                 m_iFIDParquetColumn = iParquetCol;
    1116          22 :                 iParquetCol++;
    1117             :             }
    1118         544 :             continue;
    1119             :         }
    1120             : 
    1121       26237 :         if (oSetBBOXColumns.find(field->name()) != oSetBBOXColumns.end())
    1122             :         {
    1123         522 :             m_oSetBBoxArrowColumns.insert(i);
    1124         522 :             if (bParquetColValid)
    1125         522 :                 iParquetCol++;
    1126         522 :             continue;
    1127             :         }
    1128             : 
    1129             :         const auto ComputeGeometryColumnTypeLambda =
    1130         891 :             [this, bParquetColValid, iParquetCol, &poParquetSchema]()
    1131             :         {
    1132             :             // only with GeoParquet < 0.2.0
    1133         594 :             if (bParquetColValid &&
    1134         297 :                 poParquetSchema->Column(iParquetCol)->physical_type() ==
    1135             :                     parquet::Type::BYTE_ARRAY)
    1136             :             {
    1137         297 :                 return ComputeGeometryColumnType(
    1138         594 :                     m_poFeatureDefn->GetGeomFieldCount(), iParquetCol);
    1139             :             }
    1140           0 :             return wkbUnknown;
    1141       25715 :         };
    1142             : 
    1143       77145 :         const bool bGeometryField = DealWithGeometryColumn(
    1144             :             i, field, ComputeGeometryColumnTypeLambda,
    1145       25715 :             bParquetColValid ? poParquetSchema->Column(iParquetCol) : nullptr,
    1146       25715 :             metadata.get(), bParquetColValid ? iParquetCol : -1);
    1147       25715 :         if (bGeometryField)
    1148             :         {
    1149        1030 :             const auto oIter = m_oMapGeometryColumns.find(field->name());
    1150        1030 :             if (bUseBBOX && oIter != m_oMapGeometryColumns.end())
    1151             :             {
    1152        1016 :                 ProcessGeometryColumnCovering(field, oIter->second,
    1153             :                                               oMapParquetColumnNameToIdx);
    1154             :             }
    1155             : 
    1156        3030 :             if (bParquetColValid &&
    1157        2000 :                 (field->type()->id() == arrow::Type::STRUCT ||
    1158         970 :                  field->type()->id() == arrow::Type::LIST))
    1159             :             {
    1160             :                 // GeoArrow types
    1161         493 :                 std::vector<int> anParquetCols;
    1162        3346 :                 for (const auto &iterParquetCols : oMapParquetColumnNameToIdx)
    1163             :                 {
    1164        2853 :                     if (STARTS_WITH(
    1165             :                             iterParquetCols.first.c_str(),
    1166             :                             std::string(field->name()).append(".").c_str()))
    1167             :                     {
    1168        1094 :                         iParquetCol =
    1169        1094 :                             std::max(iParquetCol, iterParquetCols.second);
    1170        1094 :                         anParquetCols.push_back(iterParquetCols.second);
    1171             :                     }
    1172             :                 }
    1173         493 :                 m_anMapGeomFieldIndexToParquetColumns.push_back(
    1174         493 :                     std::move(anParquetCols));
    1175         493 :                 ++iParquetCol;
    1176             :             }
    1177             :             else
    1178             :             {
    1179         537 :                 m_anMapGeomFieldIndexToParquetColumns.push_back(
    1180         537 :                     {bParquetColValid ? iParquetCol : -1});
    1181         537 :                 if (bParquetColValid)
    1182         537 :                     iParquetCol++;
    1183             :             }
    1184             :         }
    1185             :         else
    1186             :         {
    1187       24685 :             CreateFieldFromSchema(field, bParquetColValid, iParquetCol, {i},
    1188             :                                   oMapFieldNameToGDALSchemaFieldDefn);
    1189             :         }
    1190             :     }
    1191             : 
    1192        1054 :     CPLAssert(static_cast<int>(m_anMapFieldIndexToArrowColumn.size()) ==
    1193             :               m_poFeatureDefn->GetFieldCount());
    1194        1054 :     CPLAssert(static_cast<int>(m_anMapGeomFieldIndexToArrowColumn.size()) ==
    1195             :               m_poFeatureDefn->GetGeomFieldCount());
    1196        1054 :     CPLAssert(static_cast<int>(m_anMapGeomFieldIndexToParquetColumns.size()) ==
    1197             :               m_poFeatureDefn->GetGeomFieldCount());
    1198             : 
    1199        1054 :     if (!fields.empty())
    1200             :     {
    1201             :         try
    1202             :         {
    1203        2053 :             auto poRowGroup = m_poArrowReader->parquet_reader()->RowGroup(0);
    1204        1000 :             if (poRowGroup)
    1205             :             {
    1206        2000 :                 auto poColumn = poRowGroup->metadata()->ColumnChunk(0);
    1207        1000 :                 CPLDebug("PARQUET", "Compression (of first column): %s",
    1208             :                          arrow::util::Codec::GetCodecAsString(
    1209        1000 :                              poColumn->compression())
    1210             :                              .c_str());
    1211             :             }
    1212             :         }
    1213          53 :         catch (const std::exception &)
    1214             :         {
    1215             :         }
    1216             :     }
    1217             : }
    1218             : 
    1219             : /************************************************************************/
    1220             : /*                   ProcessGeometryColumnCovering()                    */
    1221             : /************************************************************************/
    1222             : 
    1223             : /** Process GeoParquet JSON geometry field object to extract information about
    1224             :  * its bounding box column, and appropriately fill m_oMapGeomFieldIndexToGeomColBBOX
    1225             :  * and m_oMapGeomFieldIndexToGeomColBBOXParquet members with information on that
    1226             :  * bounding box column.
    1227             :  */
    1228        1016 : void OGRParquetLayer::ProcessGeometryColumnCovering(
    1229             :     const std::shared_ptr<arrow::Field> &field,
    1230             :     const CPLJSONObject &oJSONGeometryColumn,
    1231             :     const std::map<std::string, int> &oMapParquetColumnNameToIdx)
    1232             : {
    1233        2032 :     std::string osBBOXColumn;
    1234        2032 :     std::string osXMin, osYMin, osXMax, osYMax;
    1235        1016 :     if (ParseGeometryColumnCovering(oJSONGeometryColumn, osBBOXColumn, osXMin,
    1236             :                                     osYMin, osXMax, osYMax))
    1237             :     {
    1238         522 :         OGRArrowLayer::GeomColBBOX sDesc;
    1239         522 :         sDesc.iArrowCol = m_poSchema->GetFieldIndex(osBBOXColumn);
    1240        1044 :         const auto fieldBBOX = m_poSchema->GetFieldByName(osBBOXColumn);
    1241        1044 :         if (sDesc.iArrowCol >= 0 && fieldBBOX &&
    1242         522 :             fieldBBOX->type()->id() == arrow::Type::STRUCT)
    1243             :         {
    1244             :             const auto fieldBBOXStruct =
    1245        1044 :                 std::static_pointer_cast<arrow::StructType>(fieldBBOX->type());
    1246        1044 :             const auto fieldXMin = fieldBBOXStruct->GetFieldByName(osXMin);
    1247        1044 :             const auto fieldYMin = fieldBBOXStruct->GetFieldByName(osYMin);
    1248        1044 :             const auto fieldXMax = fieldBBOXStruct->GetFieldByName(osXMax);
    1249        1044 :             const auto fieldYMax = fieldBBOXStruct->GetFieldByName(osYMax);
    1250         522 :             const int nXMinIdx = fieldBBOXStruct->GetFieldIndex(osXMin);
    1251         522 :             const int nYMinIdx = fieldBBOXStruct->GetFieldIndex(osYMin);
    1252         522 :             const int nXMaxIdx = fieldBBOXStruct->GetFieldIndex(osXMax);
    1253         522 :             const int nYMaxIdx = fieldBBOXStruct->GetFieldIndex(osYMax);
    1254             :             const auto oIterParquetIdxXMin = oMapParquetColumnNameToIdx.find(
    1255         522 :                 std::string(osBBOXColumn).append(".").append(osXMin));
    1256             :             const auto oIterParquetIdxYMin = oMapParquetColumnNameToIdx.find(
    1257         522 :                 std::string(osBBOXColumn).append(".").append(osYMin));
    1258             :             const auto oIterParquetIdxXMax = oMapParquetColumnNameToIdx.find(
    1259         522 :                 std::string(osBBOXColumn).append(".").append(osXMax));
    1260             :             const auto oIterParquetIdxYMax = oMapParquetColumnNameToIdx.find(
    1261         522 :                 std::string(osBBOXColumn).append(".").append(osYMax));
    1262         522 :             if (nXMinIdx >= 0 && nYMinIdx >= 0 && nXMaxIdx >= 0 &&
    1263        1044 :                 nYMaxIdx >= 0 && fieldXMin && fieldYMin && fieldXMax &&
    1264        1044 :                 fieldYMax &&
    1265        1044 :                 oIterParquetIdxXMin != oMapParquetColumnNameToIdx.end() &&
    1266        1044 :                 oIterParquetIdxYMin != oMapParquetColumnNameToIdx.end() &&
    1267        1044 :                 oIterParquetIdxXMax != oMapParquetColumnNameToIdx.end() &&
    1268        1044 :                 oIterParquetIdxYMax != oMapParquetColumnNameToIdx.end() &&
    1269         523 :                 (fieldXMin->type()->id() == arrow::Type::FLOAT ||
    1270           1 :                  fieldXMin->type()->id() == arrow::Type::DOUBLE) &&
    1271         522 :                 fieldXMin->type()->id() == fieldYMin->type()->id() &&
    1272        1566 :                 fieldXMin->type()->id() == fieldXMax->type()->id() &&
    1273         522 :                 fieldXMin->type()->id() == fieldYMax->type()->id())
    1274             :             {
    1275         522 :                 CPLDebug("PARQUET",
    1276             :                          "Bounding box column '%s' detected for "
    1277             :                          "geometry column '%s'",
    1278         522 :                          osBBOXColumn.c_str(), field->name().c_str());
    1279         522 :                 sDesc.iArrowSubfieldXMin = nXMinIdx;
    1280         522 :                 sDesc.iArrowSubfieldYMin = nYMinIdx;
    1281         522 :                 sDesc.iArrowSubfieldXMax = nXMaxIdx;
    1282         522 :                 sDesc.iArrowSubfieldYMax = nYMaxIdx;
    1283         522 :                 sDesc.bIsFloat =
    1284         522 :                     (fieldXMin->type()->id() == arrow::Type::FLOAT);
    1285             : 
    1286             :                 m_oMapGeomFieldIndexToGeomColBBOX
    1287         522 :                     [m_poFeatureDefn->GetGeomFieldCount() - 1] =
    1288         522 :                         std::move(sDesc);
    1289             : 
    1290         522 :                 GeomColBBOXParquet sDescParquet;
    1291         522 :                 sDescParquet.iParquetXMin = oIterParquetIdxXMin->second;
    1292         522 :                 sDescParquet.iParquetYMin = oIterParquetIdxYMin->second;
    1293         522 :                 sDescParquet.iParquetXMax = oIterParquetIdxXMax->second;
    1294         522 :                 sDescParquet.iParquetYMax = oIterParquetIdxYMax->second;
    1295        5434 :                 for (const auto &iterParquetCols : oMapParquetColumnNameToIdx)
    1296             :                 {
    1297        4912 :                     if (STARTS_WITH(
    1298             :                             iterParquetCols.first.c_str(),
    1299             :                             std::string(osBBOXColumn).append(".").c_str()))
    1300             :                     {
    1301        2088 :                         sDescParquet.anParquetCols.push_back(
    1302        2088 :                             iterParquetCols.second);
    1303             :                     }
    1304             :                 }
    1305             :                 m_oMapGeomFieldIndexToGeomColBBOXParquet
    1306        1044 :                     [m_poFeatureDefn->GetGeomFieldCount() - 1] =
    1307        1044 :                         std::move(sDescParquet);
    1308             :             }
    1309             :         }
    1310             :     }
    1311        1016 : }
    1312             : 
    1313             : /************************************************************************/
    1314             : /*                              FindNode()                              */
    1315             : /************************************************************************/
    1316             : 
    1317      291898 : static const parquet::schema::Node *FindNode(const parquet::schema::Node *node,
    1318             :                                              const std::string &arrowFieldName)
    1319             : {
    1320      291898 :     CPLAssert(node);
    1321      291898 :     if (node->name() == arrowFieldName)
    1322             :     {
    1323        4837 :         return node;
    1324             :     }
    1325      287061 :     else if (node->is_group())
    1326             :     {
    1327             :         const auto groupNode =
    1328      113420 :             cpl::down_cast<const parquet::schema::GroupNode *>(node);
    1329      395642 :         for (int i = 0; i < groupNode->field_count(); ++i)
    1330             :         {
    1331             :             const auto found =
    1332      287059 :                 FindNode(groupNode->field(i).get(), arrowFieldName);
    1333      287059 :             if (found)
    1334        4837 :                 return found;
    1335             :         }
    1336             :     }
    1337      282224 :     return nullptr;
    1338             : }
    1339             : 
    1340             : /************************************************************************/
    1341             : /*                         CollectLeaveNodes()                          */
    1342             : /************************************************************************/
    1343             : 
    1344       12811 : static void CollectLeaveNodes(
    1345             :     const parquet::schema::Node *node,
    1346             :     const std::map<const parquet::schema::Node *, int> &oMapNodeToColIdx,
    1347             :     std::vector<int> &anParquetCols)
    1348             : {
    1349       12811 :     CPLAssert(node);
    1350       12811 :     if (node->is_primitive())
    1351             :     {
    1352        6976 :         const auto it = oMapNodeToColIdx.find(node);
    1353        6976 :         if (it != oMapNodeToColIdx.end())
    1354        6976 :             anParquetCols.push_back(it->second);
    1355             :     }
    1356        5835 :     else if (node->is_group())
    1357             :     {
    1358             :         const auto groupNode =
    1359        5835 :             cpl::down_cast<const parquet::schema::GroupNode *>(node);
    1360       13809 :         for (int i = 0; i < groupNode->field_count(); ++i)
    1361             :         {
    1362        7974 :             CollectLeaveNodes(groupNode->field(i).get(), oMapNodeToColIdx,
    1363             :                               anParquetCols);
    1364             :         }
    1365             :     }
    1366       12811 : }
    1367             : 
    1368             : /************************************************************************/
    1369             : /*                GetParquetColumnIndicesForArrowField()                */
    1370             : /************************************************************************/
    1371             : 
    1372        4839 : std::vector<int> OGRParquetLayer::GetParquetColumnIndicesForArrowField(
    1373             :     const std::string &arrowFieldName) const
    1374             : {
    1375        9678 :     const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    1376        4839 :     const auto schema = metadata->schema();
    1377             : 
    1378        4839 :     std::vector<int> anParquetCols;
    1379        4839 :     const auto *rootNode = schema->schema_root().get();
    1380        4839 :     const auto *fieldNode = FindNode(rootNode, arrowFieldName);
    1381        4839 :     if (!fieldNode)
    1382             :     {
    1383           2 :         CPLDebug("Parquet",
    1384             :                  "Cannot find Parquet node corresponding to Arrow field %s",
    1385             :                  arrowFieldName.c_str());
    1386           2 :         return anParquetCols;
    1387             :     }
    1388             : 
    1389             :     /// Build mapping from schema node to column index
    1390        9674 :     std::map<const parquet::schema::Node *, int> oMapNodeToColIdx;
    1391        4837 :     const int num_cols = schema->num_columns();
    1392      505128 :     for (int i = 0; i < num_cols; ++i)
    1393             :     {
    1394      500291 :         const auto *node = schema->Column(i)->schema_node().get();
    1395      500291 :         oMapNodeToColIdx[node] = i;
    1396             :     }
    1397             : 
    1398        4837 :     CollectLeaveNodes(fieldNode, oMapNodeToColIdx, anParquetCols);
    1399             : 
    1400        4837 :     return anParquetCols;
    1401             : }
    1402             : 
    1403             : /************************************************************************/
    1404             : /*                 CheckMatchArrowParquetColumnNames()                  */
    1405             : /************************************************************************/
    1406             : 
    1407       28506 : bool OGRParquetLayer::CheckMatchArrowParquetColumnNames(
    1408             :     int &iParquetCol, const std::shared_ptr<arrow::Field> &field) const
    1409             : {
    1410       57012 :     const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    1411       28506 :     const auto poParquetSchema = metadata->schema();
    1412       28506 :     const int nParquetColumns = poParquetSchema->num_columns();
    1413       28506 :     const auto &fieldName = field->name();
    1414       28506 :     const int iParquetColBefore = iParquetCol;
    1415             : 
    1416       29290 :     while (iParquetCol < nParquetColumns)
    1417             :     {
    1418       29290 :         const auto parquetColumn = poParquetSchema->Column(iParquetCol);
    1419       29290 :         const auto parquetColumnName = parquetColumn->path()->ToDotString();
    1420       62450 :         if (fieldName == parquetColumnName ||
    1421       16580 :             (parquetColumnName.size() > fieldName.size() &&
    1422       16580 :              STARTS_WITH(parquetColumnName.c_str(), fieldName.c_str()) &&
    1423       15796 :              parquetColumnName[fieldName.size()] == '.'))
    1424             :         {
    1425       28506 :             return true;
    1426             :         }
    1427             :         else
    1428             :         {
    1429         784 :             iParquetCol++;
    1430             :         }
    1431             :     }
    1432             : 
    1433           0 :     CPLError(CE_Warning, CPLE_AppDefined,
    1434             :              "Cannot match Arrow column name %s with a Parquet one",
    1435             :              fieldName.c_str());
    1436           0 :     iParquetCol = iParquetColBefore;
    1437           0 :     return false;
    1438             : }
    1439             : 
    1440             : /************************************************************************/
    1441             : /*                       CreateFieldFromSchema()                        */
    1442             : /************************************************************************/
    1443             : 
    1444       26932 : void OGRParquetLayer::CreateFieldFromSchema(
    1445             :     const std::shared_ptr<arrow::Field> &field, bool bParquetColValid,
    1446             :     int &iParquetCol, const std::vector<int> &path,
    1447             :     const std::map<std::string, std::unique_ptr<OGRFieldDefn>>
    1448             :         &oMapFieldNameToGDALSchemaFieldDefn)
    1449             : {
    1450       26932 :     OGRFieldDefn oField(field->name().c_str(), OFTString);
    1451       26932 :     OGRFieldType eType = OFTString;
    1452       26932 :     OGRFieldSubType eSubType = OFSTNone;
    1453       26932 :     bool bTypeOK = true;
    1454             : 
    1455       26932 :     auto type = field->type();
    1456       26932 :     if (type->id() == arrow::Type::DICTIONARY && path.size() == 1)
    1457             :     {
    1458             :         const auto dictionaryType =
    1459         598 :             std::static_pointer_cast<arrow::DictionaryType>(field->type());
    1460         598 :         auto indexType = dictionaryType->index_type();
    1461         598 :         if (dictionaryType->value_type()->id() == arrow::Type::STRING &&
    1462         299 :             IsIntegerArrowType(indexType->id()))
    1463             :         {
    1464         299 :             if (bParquetColValid)
    1465             :             {
    1466         598 :                 std::string osDomainName(field->name() + "Domain");
    1467         299 :                 m_poDS->RegisterDomainName(osDomainName,
    1468         299 :                                            m_poFeatureDefn->GetFieldCount());
    1469         299 :                 oField.SetDomainName(osDomainName);
    1470             :             }
    1471         299 :             type = std::move(indexType);
    1472             :         }
    1473             :         else
    1474             :         {
    1475           0 :             bTypeOK = false;
    1476             :         }
    1477             :     }
    1478             : 
    1479       26932 :     int nParquetColIncrement = 1;
    1480       26932 :     switch (type->id())
    1481             :     {
    1482         649 :         case arrow::Type::STRUCT:
    1483             :         {
    1484        1298 :             const auto subfields = field->Flatten();
    1485             :             const std::string osExtensionName =
    1486        1298 :                 GetFieldExtensionName(field, type, GetDriverUCName().c_str());
    1487           5 :             if (osExtensionName == EXTENSION_NAME_ARROW_TIMESTAMP_WITH_OFFSET &&
    1488          10 :                 subfields.size() == 2 &&
    1489           5 :                 subfields[0]->name() ==
    1490         659 :                     field->name() + "." + ATSWO_TIMESTAMP_FIELD_NAME &&
    1491          10 :                 subfields[0]->type()->id() == arrow::Type::TIMESTAMP &&
    1492           5 :                 subfields[1]->name() ==
    1493         659 :                     field->name() + "." + ATSWO_OFFSET_MINUTES_FIELD_NAME &&
    1494           5 :                 subfields[1]->type()->id() == arrow::Type::INT16)
    1495             :             {
    1496           5 :                 oField.SetType(OFTDateTime);
    1497           5 :                 oField.SetTZFlag(OGR_TZFLAG_MIXED_TZ);
    1498           5 :                 oField.SetNullable(field->nullable());
    1499           5 :                 m_poFeatureDefn->AddFieldDefn(&oField);
    1500           5 :                 m_anMapFieldIndexToArrowColumn.push_back(path);
    1501           5 :                 m_apoArrowDataTypes.push_back(std::move(type));
    1502             :             }
    1503             :             else
    1504             :             {
    1505        1288 :                 auto newpath = path;
    1506         644 :                 newpath.push_back(0);
    1507        2891 :                 for (int j = 0; j < static_cast<int>(subfields.size()); j++)
    1508             :                 {
    1509        2247 :                     const auto &subfield = subfields[j];
    1510        2247 :                     bParquetColValid = CheckMatchArrowParquetColumnNames(
    1511             :                         iParquetCol, subfield);
    1512        2247 :                     if (!bParquetColValid)
    1513           0 :                         m_bHasMissingMappingToParquet = true;
    1514        2247 :                     newpath.back() = j;
    1515        2247 :                     CreateFieldFromSchema(subfield, bParquetColValid,
    1516             :                                           iParquetCol, newpath,
    1517             :                                           oMapFieldNameToGDALSchemaFieldDefn);
    1518             :                 }
    1519             :             }
    1520         649 :             return;  // return intended, not break
    1521             :         }
    1522             : 
    1523        5353 :         case arrow::Type::MAP:
    1524             :         {
    1525             :             // A arrow map maps to 2 Parquet columns
    1526        5353 :             nParquetColIncrement = 2;
    1527        5353 :             break;
    1528             :         }
    1529             : 
    1530       20930 :         default:
    1531       20930 :             break;
    1532             :     }
    1533             : 
    1534       26283 :     if (bTypeOK)
    1535             :     {
    1536       26283 :         bTypeOK = MapArrowTypeToOGR(type, field, oField, eType, eSubType, path,
    1537             :                                     oMapFieldNameToGDALSchemaFieldDefn);
    1538       26283 :         if (bTypeOK)
    1539             :         {
    1540       25993 :             m_apoArrowDataTypes.push_back(std::move(type));
    1541             :         }
    1542             :     }
    1543             : 
    1544       26283 :     if (bParquetColValid)
    1545       26283 :         iParquetCol += nParquetColIncrement;
    1546             : }
    1547             : 
    1548             : /************************************************************************/
    1549             : /*                            BuildDomain()                             */
    1550             : /************************************************************************/
    1551             : 
    1552             : std::unique_ptr<OGRFieldDomain>
    1553          16 : OGRParquetLayer::BuildDomain(const std::string &osDomainName,
    1554             :                              int iFieldIndex) const
    1555             : {
    1556          16 :     const int iArrowCol = m_anMapFieldIndexToArrowColumn[iFieldIndex][0];
    1557          32 :     const std::string osArrowColName = m_poSchema->fields()[iArrowCol]->name();
    1558          16 :     CPLAssert(m_poSchema->fields()[iArrowCol]->type()->id() ==
    1559             :               arrow::Type::DICTIONARY);
    1560             :     const auto anParquetColsForField =
    1561          48 :         GetParquetColumnIndicesForArrowField(osArrowColName.c_str());
    1562          16 :     CPLAssert(!anParquetColsForField.empty());
    1563          16 :     const auto oldBatchSize = m_poArrowReader->properties().batch_size();
    1564          16 :     m_poArrowReader->set_batch_size(1);
    1565             : #if PARQUET_VERSION_MAJOR >= 21
    1566             :     std::unique_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1567             :     auto result =
    1568             :         m_poArrowReader->GetRecordBatchReader({0}, anParquetColsForField);
    1569             :     if (result.ok())
    1570             :         poRecordBatchReader = std::move(*result);
    1571             : #else
    1572          16 :     std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1573          16 :     CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
    1574             :         {0}, anParquetColsForField, &poRecordBatchReader));
    1575             : #endif
    1576          16 :     if (poRecordBatchReader != nullptr)
    1577             :     {
    1578           0 :         std::shared_ptr<arrow::RecordBatch> poBatch;
    1579          16 :         auto status = poRecordBatchReader->ReadNext(&poBatch);
    1580          16 :         if (!status.ok())
    1581             :         {
    1582           0 :             CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
    1583           0 :                      status.message().c_str());
    1584             :         }
    1585          16 :         else if (poBatch)
    1586             :         {
    1587          16 :             m_poArrowReader->set_batch_size(oldBatchSize);
    1588          16 :             return BuildDomainFromBatch(osDomainName, poBatch, 0);
    1589             :         }
    1590             :     }
    1591           0 :     m_poArrowReader->set_batch_size(oldBatchSize);
    1592           0 :     return nullptr;
    1593             : }
    1594             : 
    1595             : /************************************************************************/
    1596             : /*                     ComputeGeometryColumnType()                      */
    1597             : /************************************************************************/
    1598             : 
    1599             : OGRwkbGeometryType
    1600         297 : OGRParquetLayer::ComputeGeometryColumnType(int iGeomCol, int iParquetCol) const
    1601             : {
    1602             :     // Compute type of geometry column by iterating over each geometry, and
    1603             :     // looking at the WKB geometry type in the first 5 bytes of each geometry.
    1604             : 
    1605         297 :     OGRwkbGeometryType eGeomType = wkbNone;
    1606             : 
    1607         594 :     std::vector<int> anRowGroups;
    1608         297 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    1609         297 :     anRowGroups.reserve(nNumGroups);
    1610         884 :     for (int i = 0; i < nNumGroups; ++i)
    1611         587 :         anRowGroups.push_back(i);
    1612             : #if PARQUET_VERSION_MAJOR >= 21
    1613             :     std::unique_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1614             :     auto result =
    1615             :         m_poArrowReader->GetRecordBatchReader(anRowGroups, {iParquetCol});
    1616             :     if (result.ok())
    1617             :         poRecordBatchReader = std::move(*result);
    1618             : #else
    1619           0 :     std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1620         297 :     CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
    1621             :         anRowGroups, {iParquetCol}, &poRecordBatchReader));
    1622             : #endif
    1623         297 :     if (poRecordBatchReader != nullptr)
    1624             :     {
    1625         594 :         std::shared_ptr<arrow::RecordBatch> poBatch;
    1626             :         while (true)
    1627             :         {
    1628         596 :             auto status = poRecordBatchReader->ReadNext(&poBatch);
    1629         596 :             if (!status.ok())
    1630             :             {
    1631           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
    1632           0 :                          status.message().c_str());
    1633           0 :                 break;
    1634             :             }
    1635         596 :             else if (!poBatch)
    1636         295 :                 break;
    1637             : 
    1638         301 :             eGeomType = ComputeGeometryColumnTypeProcessBatch(poBatch, iGeomCol,
    1639             :                                                               0, eGeomType);
    1640         301 :             if (eGeomType == wkbUnknown)
    1641           2 :                 break;
    1642         299 :         }
    1643             :     }
    1644             : 
    1645         594 :     return eGeomType == wkbNone ? wkbUnknown : eGeomType;
    1646             : }
    1647             : 
    1648             : /************************************************************************/
    1649             : /*                       GetFeatureExplicitFID()                        */
    1650             : /************************************************************************/
    1651             : 
    1652           4 : OGRFeature *OGRParquetLayer::GetFeatureExplicitFID(GIntBig nFID)
    1653             : {
    1654           8 :     std::vector<int> anRowGroups;
    1655           4 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    1656           4 :     anRowGroups.reserve(nNumGroups);
    1657          16 :     for (int i = 0; i < nNumGroups; ++i)
    1658          12 :         anRowGroups.push_back(i);
    1659             : #if PARQUET_VERSION_MAJOR >= 21
    1660             :     std::unique_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1661             :     auto result = m_bIgnoredFields
    1662             :                       ? m_poArrowReader->GetRecordBatchReader(
    1663             :                             anRowGroups, m_anRequestedParquetColumns)
    1664             :                       : m_poArrowReader->GetRecordBatchReader(anRowGroups);
    1665             :     if (result.ok())
    1666             :     {
    1667             :         poRecordBatchReader = std::move(*result);
    1668             :     }
    1669             : #else
    1670           4 :     std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1671           4 :     if (m_bIgnoredFields)
    1672             :     {
    1673           4 :         CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
    1674           2 :             anRowGroups, m_anRequestedParquetColumns, &poRecordBatchReader));
    1675             :     }
    1676             :     else
    1677             :     {
    1678           2 :         CPL_IGNORE_RET_VAL(m_poArrowReader->GetRecordBatchReader(
    1679             :             anRowGroups, &poRecordBatchReader));
    1680             :     }
    1681             : #endif
    1682           4 :     if (poRecordBatchReader != nullptr)
    1683             :     {
    1684           4 :         std::shared_ptr<arrow::RecordBatch> poBatch;
    1685             :         while (true)
    1686             :         {
    1687          14 :             auto status = poRecordBatchReader->ReadNext(&poBatch);
    1688          14 :             if (!status.ok())
    1689             :             {
    1690           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
    1691           0 :                          status.message().c_str());
    1692           0 :                 break;
    1693             :             }
    1694          14 :             else if (!poBatch)
    1695           2 :                 break;
    1696             : 
    1697          12 :             const auto array = poBatch->column(
    1698          12 :                 m_bIgnoredFields ? m_nRequestedFIDColumn : m_iFIDArrowColumn);
    1699          12 :             const auto arrayPtr = array.get();
    1700          12 :             const auto arrayTypeId = array->type_id();
    1701          30 :             for (int64_t nIdxInBatch = 0; nIdxInBatch < poBatch->num_rows();
    1702             :                  nIdxInBatch++)
    1703             :             {
    1704          20 :                 if (!array->IsNull(nIdxInBatch))
    1705             :                 {
    1706          20 :                     if (arrayTypeId == arrow::Type::INT64)
    1707             :                     {
    1708          20 :                         const auto castArray =
    1709             :                             static_cast<const arrow::Int64Array *>(arrayPtr);
    1710          20 :                         if (castArray->Value(nIdxInBatch) == nFID)
    1711             :                         {
    1712           2 :                             return ReadFeature(nIdxInBatch, poBatch->columns());
    1713             :                         }
    1714             :                     }
    1715           0 :                     else if (arrayTypeId == arrow::Type::INT32)
    1716             :                     {
    1717           0 :                         const auto castArray =
    1718             :                             static_cast<const arrow::Int32Array *>(arrayPtr);
    1719           0 :                         if (castArray->Value(nIdxInBatch) == nFID)
    1720             :                         {
    1721           0 :                             return ReadFeature(nIdxInBatch, poBatch->columns());
    1722             :                         }
    1723             :                     }
    1724             :                 }
    1725             :             }
    1726          10 :         }
    1727             :     }
    1728           2 :     return nullptr;
    1729             : }
    1730             : 
    1731             : /************************************************************************/
    1732             : /*                         GetFeatureByIndex()                          */
    1733             : /************************************************************************/
    1734             : 
    1735          64 : OGRFeature *OGRParquetLayer::GetFeatureByIndex(GIntBig nFID)
    1736             : {
    1737             : 
    1738          64 :     if (nFID < 0)
    1739           5 :         return nullptr;
    1740             : 
    1741         118 :     const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    1742          59 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    1743          59 :     int64_t nAccRows = 0;
    1744          72 :     for (int iGroup = 0; iGroup < nNumGroups; ++iGroup)
    1745             :     {
    1746             :         const int64_t nNextAccRows =
    1747          63 :             nAccRows + metadata->RowGroup(iGroup)->num_rows();
    1748          63 :         if (nFID < nNextAccRows)
    1749             :         {
    1750             : #if PARQUET_VERSION_MAJOR >= 21
    1751             :             std::unique_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1752             :             auto result = m_bIgnoredFields
    1753             :                               ? m_poArrowReader->GetRecordBatchReader(
    1754             :                                     {iGroup}, m_anRequestedParquetColumns)
    1755             :                               : m_poArrowReader->GetRecordBatchReader({iGroup});
    1756             :             if (result.ok())
    1757             :             {
    1758             :                 poRecordBatchReader = std::move(*result);
    1759             :             }
    1760             :             else
    1761             :             {
    1762             :                 CPLError(CE_Failure, CPLE_AppDefined,
    1763             :                          "GetRecordBatchReader() failed: %s",
    1764             :                          result.status().message().c_str());
    1765             :                 return nullptr;
    1766             :             }
    1767             : #else
    1768          50 :             std::shared_ptr<arrow::RecordBatchReader> poRecordBatchReader;
    1769             :             {
    1770           0 :                 arrow::Status status;
    1771          50 :                 if (m_bIgnoredFields)
    1772             :                 {
    1773           0 :                     status = m_poArrowReader->GetRecordBatchReader(
    1774           0 :                         {iGroup}, m_anRequestedParquetColumns,
    1775           0 :                         &poRecordBatchReader);
    1776             :                 }
    1777             :                 else
    1778             :                 {
    1779         100 :                     status = m_poArrowReader->GetRecordBatchReader(
    1780          50 :                         {iGroup}, &poRecordBatchReader);
    1781             :                 }
    1782          50 :                 if (poRecordBatchReader == nullptr)
    1783             :                 {
    1784           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1785             :                              "GetRecordBatchReader() failed: %s",
    1786           0 :                              status.message().c_str());
    1787           0 :                     return nullptr;
    1788             :                 }
    1789             :             }
    1790             : #endif
    1791             : 
    1792          50 :             const int64_t nExpectedIdxInGroup = nFID - nAccRows;
    1793          50 :             int64_t nIdxInGroup = 0;
    1794             :             while (true)
    1795             :             {
    1796           0 :                 std::shared_ptr<arrow::RecordBatch> poBatch;
    1797          50 :                 arrow::Status status = poRecordBatchReader->ReadNext(&poBatch);
    1798          50 :                 if (!status.ok())
    1799             :                 {
    1800           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1801           0 :                              "ReadNext() failed: %s", status.message().c_str());
    1802           0 :                     return nullptr;
    1803             :                 }
    1804          50 :                 if (poBatch == nullptr)
    1805             :                 {
    1806           0 :                     return nullptr;
    1807             :                 }
    1808          50 :                 if (nExpectedIdxInGroup < nIdxInGroup + poBatch->num_rows())
    1809             :                 {
    1810          50 :                     const auto nIdxInBatch = nExpectedIdxInGroup - nIdxInGroup;
    1811             :                     auto poFeature =
    1812          50 :                         ReadFeature(nIdxInBatch, poBatch->columns());
    1813          50 :                     poFeature->SetFID(nFID);
    1814          50 :                     return poFeature;
    1815             :                 }
    1816           0 :                 nIdxInGroup += poBatch->num_rows();
    1817           0 :             }
    1818             :         }
    1819          13 :         nAccRows = nNextAccRows;
    1820             :     }
    1821           9 :     return nullptr;
    1822             : }
    1823             : 
    1824             : /************************************************************************/
    1825             : /*                             GetFeature()                             */
    1826             : /************************************************************************/
    1827             : 
    1828          68 : OGRFeature *OGRParquetLayer::GetFeature(GIntBig nFID)
    1829             : {
    1830          68 :     if (!m_osFIDColumn.empty())
    1831             :     {
    1832           4 :         return GetFeatureExplicitFID(nFID);
    1833             :     }
    1834             :     else
    1835             :     {
    1836          64 :         return GetFeatureByIndex(nFID);
    1837             :     }
    1838             : }
    1839             : 
    1840             : /************************************************************************/
    1841             : /*                            ResetReading()                            */
    1842             : /************************************************************************/
    1843             : 
    1844        4561 : void OGRParquetLayer::ResetReading()
    1845             : {
    1846        4561 :     OGRParquetLayerBase::ResetReading();
    1847        4561 :     m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
    1848        4561 :     m_nFeatureIdxSelected = 0;
    1849        4561 :     if (!m_asFeatureIdxRemapping.empty())
    1850             :     {
    1851        2202 :         m_nFeatureIdx = m_oFeatureIdxRemappingIter->second;
    1852        2202 :         ++m_oFeatureIdxRemappingIter;
    1853             :     }
    1854        4561 : }
    1855             : 
    1856             : /************************************************************************/
    1857             : /*                      CreateRecordBatchReader()                       */
    1858             : /************************************************************************/
    1859             : 
    1860         687 : bool OGRParquetLayer::CreateRecordBatchReader(int iStartingRowGroup)
    1861             : {
    1862        1374 :     std::vector<int> anRowGroups;
    1863         687 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    1864         687 :     anRowGroups.reserve(nNumGroups - iStartingRowGroup);
    1865        1727 :     for (int i = iStartingRowGroup; i < nNumGroups; ++i)
    1866        1040 :         anRowGroups.push_back(i);
    1867        1374 :     return CreateRecordBatchReader(anRowGroups);
    1868             : }
    1869             : 
    1870         987 : bool OGRParquetLayer::CreateRecordBatchReader(
    1871             :     const std::vector<int> &anRowGroups)
    1872             : {
    1873             : #if PARQUET_VERSION_MAJOR >= 21
    1874             :     auto result = m_bIgnoredFields
    1875             :                       ? m_poArrowReader->GetRecordBatchReader(
    1876             :                             anRowGroups, m_anRequestedParquetColumns)
    1877             :                       : m_poArrowReader->GetRecordBatchReader(anRowGroups);
    1878             :     if (result.ok())
    1879             :     {
    1880             :         m_poRecordBatchReader = std::move(*result);
    1881             :         return true;
    1882             :     }
    1883             :     else
    1884             :     {
    1885             :         CPLError(CE_Failure, CPLE_AppDefined,
    1886             :                  "GetRecordBatchReader() failed: %s",
    1887             :                  result.status().message().c_str());
    1888             :         return false;
    1889             :     }
    1890             : #else
    1891         987 :     arrow::Status status;
    1892         987 :     if (m_bIgnoredFields)
    1893             :     {
    1894         470 :         status = m_poArrowReader->GetRecordBatchReader(
    1895         235 :             anRowGroups, m_anRequestedParquetColumns, &m_poRecordBatchReader);
    1896             :     }
    1897             :     else
    1898             :     {
    1899        1504 :         status = m_poArrowReader->GetRecordBatchReader(anRowGroups,
    1900         752 :                                                        &m_poRecordBatchReader);
    1901             :     }
    1902         987 :     if (m_poRecordBatchReader == nullptr)
    1903             :     {
    1904           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1905           0 :                  "GetRecordBatchReader() failed: %s", status.message().c_str());
    1906           0 :         return false;
    1907             :     }
    1908         987 :     return true;
    1909             : #endif
    1910             : }
    1911             : 
    1912             : /************************************************************************/
    1913             : /*                        IsConstraintPossible()                        */
    1914             : /************************************************************************/
    1915             : 
    1916             : enum class IsConstraintPossibleRes
    1917             : {
    1918             :     YES,
    1919             :     NO,
    1920             :     UNKNOWN
    1921             : };
    1922             : 
    1923             : template <class T>
    1924         224 : static IsConstraintPossibleRes IsConstraintPossible(int nOperation, T v, T min,
    1925             :                                                     T max)
    1926             : {
    1927         224 :     if (nOperation == SWQ_EQ)
    1928             :     {
    1929         146 :         if (v < min || v > max)
    1930             :         {
    1931          59 :             return IsConstraintPossibleRes::NO;
    1932             :         }
    1933             :     }
    1934          78 :     else if (nOperation == SWQ_NE)
    1935             :     {
    1936          38 :         if (v == min && v == max)
    1937             :         {
    1938           0 :             return IsConstraintPossibleRes::NO;
    1939             :         }
    1940             :     }
    1941          40 :     else if (nOperation == SWQ_LE)
    1942             :     {
    1943          10 :         if (v < min)
    1944             :         {
    1945           4 :             return IsConstraintPossibleRes::NO;
    1946             :         }
    1947             :     }
    1948          30 :     else if (nOperation == SWQ_LT)
    1949             :     {
    1950          10 :         if (v <= min)
    1951             :         {
    1952           4 :             return IsConstraintPossibleRes::NO;
    1953             :         }
    1954             :     }
    1955          20 :     else if (nOperation == SWQ_GE)
    1956             :     {
    1957          10 :         if (v > max)
    1958             :         {
    1959           4 :             return IsConstraintPossibleRes::NO;
    1960             :         }
    1961             :     }
    1962          10 :     else if (nOperation == SWQ_GT)
    1963             :     {
    1964          10 :         if (v >= max)
    1965             :         {
    1966           6 :             return IsConstraintPossibleRes::NO;
    1967             :         }
    1968             :     }
    1969             :     else
    1970             :     {
    1971           0 :         CPLDebug("PARQUET",
    1972             :                  "IsConstraintPossible: Unhandled operation type: %d",
    1973             :                  nOperation);
    1974           0 :         return IsConstraintPossibleRes::UNKNOWN;
    1975             :     }
    1976         147 :     return IsConstraintPossibleRes::YES;
    1977             : }
    1978             : 
    1979             : /************************************************************************/
    1980             : /*                           IncrFeatureIdx()                           */
    1981             : /************************************************************************/
    1982             : 
    1983        8178 : void OGRParquetLayer::IncrFeatureIdx()
    1984             : {
    1985        8178 :     ++m_nFeatureIdxSelected;
    1986        8178 :     ++m_nFeatureIdx;
    1987        9333 :     if (m_iFIDArrowColumn < 0 && !m_asFeatureIdxRemapping.empty() &&
    1988        9333 :         m_oFeatureIdxRemappingIter != m_asFeatureIdxRemapping.end())
    1989             :     {
    1990         140 :         if (m_nFeatureIdxSelected == m_oFeatureIdxRemappingIter->first)
    1991             :         {
    1992          48 :             m_nFeatureIdx = m_oFeatureIdxRemappingIter->second;
    1993          48 :             ++m_oFeatureIdxRemappingIter;
    1994             :         }
    1995             :     }
    1996        8178 : }
    1997             : 
    1998             : /************************************************************************/
    1999             : /*                           ReadNextBatch()                            */
    2000             : /************************************************************************/
    2001             : 
    2002        2113 : bool OGRParquetLayer::ReadNextBatch()
    2003             : {
    2004        2113 :     m_nIdxInBatch = 0;
    2005             : 
    2006        2113 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    2007        2113 :     if (nNumGroups == 0)
    2008           2 :         return false;
    2009             : 
    2010        2111 :     if (m_bSingleBatch)
    2011             :     {
    2012          32 :         CPLAssert(m_iRecordBatch == 0);
    2013          32 :         CPLAssert(m_poBatch != nullptr);
    2014          32 :         return false;
    2015             :     }
    2016             : 
    2017        2079 :     CPLAssert((m_iRecordBatch == -1 && m_poRecordBatchReader == nullptr) ||
    2018             :               (m_iRecordBatch >= 0 && m_poRecordBatchReader != nullptr));
    2019             : 
    2020        2079 :     if (m_poRecordBatchReader == nullptr)
    2021             :     {
    2022         993 :         m_asFeatureIdxRemapping.clear();
    2023             : 
    2024         993 :         bool bIterateEverything = false;
    2025         993 :         std::vector<int> anSelectedGroups;
    2026             :         const auto oIterToGeomColBBOX =
    2027         993 :             m_oMapGeomFieldIndexToGeomColBBOXParquet.find(m_iGeomFieldFilter);
    2028             :         const bool bUSEBBOXFields =
    2029         243 :             (m_poFilterGeom &&
    2030         243 :              oIterToGeomColBBOX !=
    2031        1236 :                  m_oMapGeomFieldIndexToGeomColBBOXParquet.end() &&
    2032         141 :              CPLTestBool(CPLGetConfigOption(
    2033        1134 :                  ("OGR_" + GetDriverUCName() + "_USE_BBOX").c_str(), "YES")));
    2034             :         const bool bIsGeoArrowStruct =
    2035        1986 :             (m_iGeomFieldFilter >= 0 &&
    2036         993 :              m_iGeomFieldFilter < static_cast<int>(m_aeGeomEncoding.size()) &&
    2037         985 :              m_iGeomFieldFilter <
    2038             :                  static_cast<int>(
    2039        1970 :                      m_anMapGeomFieldIndexToParquetColumns.size()) &&
    2040         985 :              m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter].size() >=
    2041        1986 :                  2 &&
    2042         306 :              OGRArrowIsGeoArrowStruct(m_aeGeomEncoding[m_iGeomFieldFilter]));
    2043             : #if PARQUET_VERSION_MAJOR >= 21
    2044             :         const bool bUseParquetGeoStat =
    2045             :             (m_poFilterGeom && m_iGeomFieldFilter >= 0 &&
    2046             :              m_geoStatsWithBBOXAvailable.find(m_iGeomFieldFilter) !=
    2047             :                  m_geoStatsWithBBOXAvailable.end() &&
    2048             :              m_iGeomFieldFilter <
    2049             :                  static_cast<int>(
    2050             :                      m_anMapGeomFieldIndexToParquetColumns.size()) &&
    2051             :              m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter].size() ==
    2052             :                  1 &&
    2053             :              m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter][0] >= 0);
    2054             : #endif
    2055        1712 :         if (m_asAttributeFilterConstraints.empty() && !bUSEBBOXFields &&
    2056         719 :             !(bIsGeoArrowStruct && m_poFilterGeom)
    2057             : #if PARQUET_VERSION_MAJOR >= 21
    2058             :             && !bUseParquetGeoStat
    2059             : #endif
    2060             :         )
    2061             :         {
    2062         673 :             bIterateEverything = true;
    2063             :         }
    2064             :         else
    2065             :         {
    2066             :             OGRField sMin;
    2067             :             OGRField sMax;
    2068         320 :             OGR_RawField_SetNull(&sMin);
    2069         320 :             OGR_RawField_SetNull(&sMax);
    2070         320 :             bool bFoundMin = false;
    2071         320 :             bool bFoundMax = false;
    2072         320 :             OGRFieldType eType = OFTMaxType;
    2073         320 :             OGRFieldSubType eSubType = OFSTNone;
    2074         640 :             std::string osMinTmp, osMaxTmp;
    2075         320 :             int64_t nFeatureIdxSelected = 0;
    2076         320 :             int64_t nFeatureIdxTotal = 0;
    2077             : 
    2078         320 :             int iXMinField = -1;
    2079         320 :             int iYMinField = -1;
    2080         320 :             int iXMaxField = -1;
    2081         320 :             int iYMaxField = -1;
    2082             : 
    2083         320 :             if (bIsGeoArrowStruct)
    2084             :             {
    2085             :                 const auto metadata =
    2086         276 :                     m_poArrowReader->parquet_reader()->metadata();
    2087         138 :                 const auto poParquetSchema = metadata->schema();
    2088         342 :                 for (int iParquetCol :
    2089         822 :                      m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter])
    2090             :                 {
    2091             :                     const auto parquetColumn =
    2092         342 :                         poParquetSchema->Column(iParquetCol);
    2093             :                     const auto parquetColumnName =
    2094         684 :                         parquetColumn->path()->ToDotString();
    2095         684 :                     if (parquetColumnName.size() > 2 &&
    2096         342 :                         parquetColumnName.find(".x") ==
    2097         342 :                             parquetColumnName.size() - 2)
    2098             :                     {
    2099         138 :                         iXMinField = iParquetCol;
    2100         138 :                         iXMaxField = iParquetCol;
    2101             :                     }
    2102         408 :                     else if (parquetColumnName.size() > 2 &&
    2103         204 :                              parquetColumnName.find(".y") ==
    2104         204 :                                  parquetColumnName.size() - 2)
    2105             :                     {
    2106         138 :                         iYMinField = iParquetCol;
    2107         138 :                         iYMaxField = iParquetCol;
    2108             :                     }
    2109             :                 }
    2110             :             }
    2111         182 :             else if (bUSEBBOXFields)
    2112             :             {
    2113          49 :                 iXMinField = oIterToGeomColBBOX->second.iParquetXMin;
    2114          49 :                 iYMinField = oIterToGeomColBBOX->second.iParquetYMin;
    2115          49 :                 iXMaxField = oIterToGeomColBBOX->second.iParquetXMax;
    2116          49 :                 iYMaxField = oIterToGeomColBBOX->second.iParquetYMax;
    2117             :             }
    2118             : 
    2119         765 :             for (int iRowGroup = 0;
    2120         765 :                  iRowGroup < nNumGroups && !bIterateEverything; ++iRowGroup)
    2121             :             {
    2122         445 :                 bool bSelectGroup = true;
    2123             :                 auto poRowGroup =
    2124         445 :                     GetReader()->parquet_reader()->RowGroup(iRowGroup);
    2125             : 
    2126         445 :                 if (iXMinField >= 0 && iYMinField >= 0 && iXMaxField >= 0 &&
    2127             :                     iYMaxField >= 0)
    2128             :                 {
    2129         195 :                     if (GetMinMaxForParquetCol(iRowGroup, iXMinField, nullptr,
    2130             :                                                true, sMin, bFoundMin, false,
    2131             :                                                sMax, bFoundMax, eType, eSubType,
    2132         194 :                                                osMinTmp, osMaxTmp) &&
    2133         389 :                         bFoundMin && eType == OFTReal)
    2134             :                     {
    2135         194 :                         const double dfGroupMinX = sMin.Real;
    2136         194 :                         if (dfGroupMinX > m_sFilterEnvelope.MaxX)
    2137             :                         {
    2138           1 :                             bSelectGroup = false;
    2139             :                         }
    2140         193 :                         else if (GetMinMaxForParquetCol(
    2141             :                                      iRowGroup, iYMinField, nullptr, true, sMin,
    2142             :                                      bFoundMin, false, sMax, bFoundMax, eType,
    2143         193 :                                      eSubType, osMinTmp, osMaxTmp) &&
    2144         386 :                                  bFoundMin && eType == OFTReal)
    2145             :                         {
    2146         193 :                             const double dfGroupMinY = sMin.Real;
    2147         193 :                             if (dfGroupMinY > m_sFilterEnvelope.MaxY)
    2148             :                             {
    2149           1 :                                 bSelectGroup = false;
    2150             :                             }
    2151         192 :                             else if (GetMinMaxForParquetCol(
    2152             :                                          iRowGroup, iXMaxField, nullptr, false,
    2153             :                                          sMin, bFoundMin, true, sMax, bFoundMax,
    2154         192 :                                          eType, eSubType, osMinTmp, osMaxTmp) &&
    2155         384 :                                      bFoundMax && eType == OFTReal)
    2156             :                             {
    2157         192 :                                 const double dfGroupMaxX = sMax.Real;
    2158         192 :                                 if (dfGroupMaxX < m_sFilterEnvelope.MinX)
    2159             :                                 {
    2160           1 :                                     bSelectGroup = false;
    2161             :                                 }
    2162         191 :                                 else if (GetMinMaxForParquetCol(
    2163             :                                              iRowGroup, iYMaxField, nullptr,
    2164             :                                              false, sMin, bFoundMin, true, sMax,
    2165             :                                              bFoundMax, eType, eSubType,
    2166         191 :                                              osMinTmp, osMaxTmp) &&
    2167         382 :                                          bFoundMax && eType == OFTReal)
    2168             :                                 {
    2169         191 :                                     const double dfGroupMaxY = sMax.Real;
    2170         191 :                                     if (dfGroupMaxY < m_sFilterEnvelope.MinY)
    2171             :                                     {
    2172           1 :                                         bSelectGroup = false;
    2173             :                                     }
    2174             :                                 }
    2175             :                             }
    2176             :                         }
    2177             :                     }
    2178             :                 }
    2179             : #if PARQUET_VERSION_MAJOR >= 21
    2180             :                 else if (bUseParquetGeoStat)
    2181             :                 {
    2182             :                     const int iParquetCol =
    2183             :                         m_anMapGeomFieldIndexToParquetColumns
    2184             :                             [m_iGeomFieldFilter][0];
    2185             :                     CPLAssert(iParquetCol >= 0);
    2186             : 
    2187             :                     const auto metadata =
    2188             :                         m_poArrowReader->parquet_reader()->metadata();
    2189             :                     const auto columnChunk =
    2190             :                         metadata->RowGroup(iRowGroup)->ColumnChunk(iParquetCol);
    2191             :                     if (auto geostats = columnChunk->geo_statistics())
    2192             :                     {
    2193             :                         if (geostats->dimension_valid()[0] &&
    2194             :                             geostats->dimension_valid()[1])
    2195             :                         {
    2196             :                             double dfMinX = geostats->lower_bound()[0];
    2197             :                             double dfMaxX = geostats->upper_bound()[0];
    2198             :                             double dfMinY = geostats->lower_bound()[1];
    2199             :                             double dfMaxY = geostats->upper_bound()[1];
    2200             : 
    2201             :                             // Deal as best as we can with wrap around bounding box
    2202             :                             if (dfMinX > dfMaxX && std::fabs(dfMinX) <= 180 &&
    2203             :                                 std::fabs(dfMaxX) <= 180)
    2204             :                             {
    2205             :                                 dfMinX = -180;
    2206             :                                 dfMaxX = 180;
    2207             :                             }
    2208             : 
    2209             :                             // Check if there is an intersection between
    2210             :                             // the geostatistics for this rowgroup and
    2211             :                             // the bbox of interest
    2212             :                             if (dfMinX > m_sFilterEnvelope.MaxX ||
    2213             :                                 dfMaxX < m_sFilterEnvelope.MinX ||
    2214             :                                 dfMinY > m_sFilterEnvelope.MaxY ||
    2215             :                                 dfMaxY < m_sFilterEnvelope.MinY)
    2216             :                             {
    2217             :                                 bSelectGroup = false;
    2218             :                             }
    2219             :                         }
    2220             :                     }
    2221             :                 }
    2222             : #endif
    2223             : 
    2224         445 :                 if (bSelectGroup)
    2225             :                 {
    2226         604 :                     for (auto &constraint : m_asAttributeFilterConstraints)
    2227             :                     {
    2228         254 :                         int iOGRField = constraint.iField;
    2229         508 :                         if (constraint.iField ==
    2230         254 :                             m_poFeatureDefn->GetFieldCount() + SPF_FID)
    2231             :                         {
    2232           9 :                             iOGRField = OGR_FID_INDEX;
    2233             :                         }
    2234         254 :                         if (constraint.nOperation != SWQ_ISNULL &&
    2235         245 :                             constraint.nOperation != SWQ_ISNOTNULL)
    2236             :                         {
    2237         232 :                             if (iOGRField == OGR_FID_INDEX &&
    2238           9 :                                 m_iFIDParquetColumn < 0)
    2239             :                             {
    2240           6 :                                 sMin.Integer64 = nFeatureIdxTotal;
    2241           6 :                                 sMax.Integer64 =
    2242           6 :                                     nFeatureIdxTotal +
    2243           6 :                                     poRowGroup->metadata()->num_rows() - 1;
    2244           6 :                                 eType = OFTInteger64;
    2245             :                             }
    2246         226 :                             else if (!GetMinMaxForOGRField(
    2247             :                                          iRowGroup, iOGRField, true, sMin,
    2248             :                                          bFoundMin, true, sMax, bFoundMax,
    2249         221 :                                          eType, eSubType, osMinTmp, osMaxTmp) ||
    2250         226 :                                      !bFoundMin || !bFoundMax)
    2251             :                             {
    2252           5 :                                 bIterateEverything = true;
    2253           5 :                                 break;
    2254             :                             }
    2255             :                         }
    2256             : 
    2257         249 :                         IsConstraintPossibleRes res =
    2258             :                             IsConstraintPossibleRes::UNKNOWN;
    2259         249 :                         if (constraint.eType ==
    2260         147 :                                 OGRArrowLayer::Constraint::Type::Integer &&
    2261         147 :                             eType == OFTInteger)
    2262             :                         {
    2263             : #if 0
    2264             :                             CPLDebug("PARQUET",
    2265             :                                      "Group %d, field %s, min = %d, max = %d",
    2266             :                                      iRowGroup,
    2267             :                                      iOGRField == OGR_FID_INDEX
    2268             :                                          ? m_osFIDColumn.c_str()
    2269             :                                          : m_poFeatureDefn->GetFieldDefn(iOGRField)
    2270             :                                                ->GetNameRef(),
    2271             :                                      sMin.Integer, sMax.Integer);
    2272             : #endif
    2273         125 :                             res = IsConstraintPossible(
    2274             :                                 constraint.nOperation,
    2275             :                                 constraint.sValue.Integer, sMin.Integer,
    2276             :                                 sMax.Integer);
    2277             :                         }
    2278         124 :                         else if (constraint.eType == OGRArrowLayer::Constraint::
    2279          35 :                                                          Type::Integer64 &&
    2280          35 :                                  eType == OFTInteger64)
    2281             :                         {
    2282             : #if 0
    2283             :                             CPLDebug("PARQUET",
    2284             :                                      "Group %d, field %s, min = " CPL_FRMT_GIB
    2285             :                                      ", max = " CPL_FRMT_GIB,
    2286             :                                      iRowGroup,
    2287             :                                      iOGRField == OGR_FID_INDEX
    2288             :                                          ? m_osFIDColumn.c_str()
    2289             :                                          : m_poFeatureDefn->GetFieldDefn(iOGRField)
    2290             :                                                ->GetNameRef(),
    2291             :                                      static_cast<GIntBig>(sMin.Integer64),
    2292             :                                      static_cast<GIntBig>(sMax.Integer64));
    2293             : #endif
    2294          35 :                             res = IsConstraintPossible(
    2295             :                                 constraint.nOperation,
    2296             :                                 constraint.sValue.Integer64, sMin.Integer64,
    2297             :                                 sMax.Integer64);
    2298             :                         }
    2299          89 :                         else if (constraint.eType ==
    2300          29 :                                      OGRArrowLayer::Constraint::Type::Real &&
    2301          29 :                                  eType == OFTReal)
    2302             :                         {
    2303             : #if 0
    2304             :                             CPLDebug("PARQUET",
    2305             :                                      "Group %d, field %s, min = %g, max = %g",
    2306             :                                      iRowGroup,
    2307             :                                      iOGRField == OGR_FID_INDEX
    2308             :                                          ? m_osFIDColumn.c_str()
    2309             :                                          : m_poFeatureDefn->GetFieldDefn(iOGRField)
    2310             :                                                ->GetNameRef(),
    2311             :                                      sMin.Real, sMax.Real);
    2312             : #endif
    2313          26 :                             res = IsConstraintPossible(constraint.nOperation,
    2314             :                                                        constraint.sValue.Real,
    2315             :                                                        sMin.Real, sMax.Real);
    2316             :                         }
    2317          63 :                         else if (constraint.eType ==
    2318          38 :                                      OGRArrowLayer::Constraint::Type::String &&
    2319          38 :                                  eType == OFTString)
    2320             :                         {
    2321             : #if 0
    2322             :                             CPLDebug("PARQUET",
    2323             :                                      "Group %d, field %s, min = %s, max = %s",
    2324             :                                      iRowGroup,
    2325             :                                      iOGRField == OGR_FID_INDEX
    2326             :                                          ? m_osFIDColumn.c_str()
    2327             :                                          : m_poFeatureDefn->GetFieldDefn(iOGRField)
    2328             :                                                ->GetNameRef(),
    2329             :                                      sMin.String, sMax.String);
    2330             : #endif
    2331          38 :                             res = IsConstraintPossible(
    2332             :                                 constraint.nOperation,
    2333          76 :                                 std::string(constraint.sValue.String),
    2334          76 :                                 std::string(sMin.String),
    2335          76 :                                 std::string(sMax.String));
    2336             :                         }
    2337          25 :                         else if (constraint.nOperation == SWQ_ISNULL ||
    2338          16 :                                  constraint.nOperation == SWQ_ISNOTNULL)
    2339             :                         {
    2340             :                             const std::vector<int> anCols =
    2341             :                                 iOGRField == OGR_FID_INDEX
    2342           0 :                                     ? std::vector<int>{m_iFIDParquetColumn}
    2343             :                                     : GetParquetColumnIndicesForArrowField(
    2344          44 :                                           GetLayerDefn()
    2345          22 :                                               ->GetFieldDefn(iOGRField)
    2346          88 :                                               ->GetNameRef());
    2347          22 :                             if (anCols.size() == 1 && anCols[0] >= 0)
    2348             :                             {
    2349             :                                 const auto metadata =
    2350          22 :                                     m_poArrowReader->parquet_reader()
    2351          44 :                                         ->metadata();
    2352             :                                 const auto rowGroupColumnChunk =
    2353          22 :                                     metadata->RowGroup(iRowGroup)->ColumnChunk(
    2354          44 :                                         anCols[0]);
    2355             :                                 const auto rowGroupStats =
    2356          44 :                                     rowGroupColumnChunk->statistics();
    2357          44 :                                 if (rowGroupColumnChunk->is_stats_set() &&
    2358          22 :                                     rowGroupStats)
    2359             :                                 {
    2360          22 :                                     res = IsConstraintPossibleRes::YES;
    2361          31 :                                     if (constraint.nOperation == SWQ_ISNULL &&
    2362           9 :                                         rowGroupStats->num_values() ==
    2363           9 :                                             poRowGroup->metadata()->num_rows())
    2364             :                                     {
    2365           5 :                                         res = IsConstraintPossibleRes::NO;
    2366             :                                     }
    2367          34 :                                     else if (constraint.nOperation ==
    2368          30 :                                                  SWQ_ISNOTNULL &&
    2369          13 :                                              rowGroupStats->num_values() == 0)
    2370             :                                     {
    2371           1 :                                         res = IsConstraintPossibleRes::NO;
    2372             :                                     }
    2373             :                                 }
    2374          22 :                             }
    2375             :                         }
    2376             :                         else
    2377             :                         {
    2378           3 :                             CPLDebug(
    2379             :                                 "PARQUET",
    2380             :                                 "Unhandled combination of constraint.eType "
    2381             :                                 "(%d) and eType (%d)",
    2382           3 :                                 static_cast<int>(constraint.eType), eType);
    2383             :                         }
    2384             : 
    2385         249 :                         if (res == IsConstraintPossibleRes::NO)
    2386             :                         {
    2387          83 :                             bSelectGroup = false;
    2388          83 :                             break;
    2389             :                         }
    2390         166 :                         else if (res == IsConstraintPossibleRes::UNKNOWN)
    2391             :                         {
    2392           3 :                             bIterateEverything = true;
    2393           3 :                             break;
    2394             :                         }
    2395             :                     }
    2396             :                 }
    2397             : 
    2398         445 :                 if (bSelectGroup)
    2399             :                 {
    2400             :                     // CPLDebug("PARQUET", "Selecting row group %d", iRowGroup);
    2401             :                     m_asFeatureIdxRemapping.emplace_back(
    2402         358 :                         std::make_pair(nFeatureIdxSelected, nFeatureIdxTotal));
    2403         358 :                     anSelectedGroups.push_back(iRowGroup);
    2404         358 :                     nFeatureIdxSelected += poRowGroup->metadata()->num_rows();
    2405             :                 }
    2406             : 
    2407         445 :                 nFeatureIdxTotal += poRowGroup->metadata()->num_rows();
    2408             :             }
    2409             :         }
    2410             : 
    2411         993 :         if (bIterateEverything)
    2412             :         {
    2413         681 :             m_asFeatureIdxRemapping.clear();
    2414         681 :             m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
    2415         681 :             if (!CreateRecordBatchReader(0))
    2416           0 :                 return false;
    2417             :         }
    2418             :         else
    2419             :         {
    2420         312 :             m_oFeatureIdxRemappingIter = m_asFeatureIdxRemapping.begin();
    2421         312 :             if (anSelectedGroups.empty())
    2422             :             {
    2423          12 :                 return false;
    2424             :             }
    2425         300 :             CPLDebug("PARQUET", "%d/%d row groups selected",
    2426         300 :                      int(anSelectedGroups.size()),
    2427         300 :                      m_poArrowReader->num_row_groups());
    2428         300 :             m_nFeatureIdx = m_oFeatureIdxRemappingIter->second;
    2429         300 :             ++m_oFeatureIdxRemappingIter;
    2430         300 :             if (!CreateRecordBatchReader(anSelectedGroups))
    2431             :             {
    2432           0 :                 return false;
    2433             :             }
    2434             :         }
    2435             :     }
    2436             : 
    2437        4134 :     std::shared_ptr<arrow::RecordBatch> poNextBatch;
    2438             : 
    2439           0 :     do
    2440             :     {
    2441        2067 :         ++m_iRecordBatch;
    2442        2067 :         poNextBatch.reset();
    2443        2067 :         auto status = m_poRecordBatchReader->ReadNext(&poNextBatch);
    2444        2067 :         if (!status.ok())
    2445             :         {
    2446           0 :             CPLError(CE_Failure, CPLE_AppDefined, "ReadNext() failed: %s",
    2447           0 :                      status.message().c_str());
    2448           0 :             poNextBatch.reset();
    2449             :         }
    2450        2067 :         if (poNextBatch == nullptr)
    2451             :         {
    2452        1113 :             if (m_iRecordBatch == 1 && m_poBatch && m_poAttrQuery == nullptr &&
    2453         365 :                 m_poFilterGeom == nullptr)
    2454             :             {
    2455          59 :                 m_iRecordBatch = 0;
    2456          59 :                 m_bSingleBatch = true;
    2457             :             }
    2458             :             else
    2459         689 :                 m_poBatch.reset();
    2460         748 :             return false;
    2461             :         }
    2462        1319 :     } while (poNextBatch->num_rows() == 0);
    2463             : 
    2464        1319 :     SetBatch(poNextBatch);
    2465             : 
    2466        1319 :     return true;
    2467             : }
    2468             : 
    2469             : /************************************************************************/
    2470             : /*                      InvalidateCachedBatches()                       */
    2471             : /************************************************************************/
    2472             : 
    2473         953 : void OGRParquetLayer::InvalidateCachedBatches()
    2474             : {
    2475         953 :     m_bSingleBatch = false;
    2476         953 :     OGRParquetLayerBase::InvalidateCachedBatches();
    2477         953 : }
    2478             : 
    2479             : /************************************************************************/
    2480             : /*                          SetIgnoredFields()                          */
    2481             : /************************************************************************/
    2482             : 
    2483         260 : OGRErr OGRParquetLayer::SetIgnoredFields(CSLConstList papszFields)
    2484             : {
    2485         260 :     m_bIgnoredFields = false;
    2486         260 :     m_anRequestedParquetColumns.clear();
    2487         260 :     m_anMapFieldIndexToArrayIndex.clear();
    2488         260 :     m_anMapGeomFieldIndexToArrayIndex.clear();
    2489         260 :     m_nRequestedFIDColumn = -1;
    2490         260 :     OGRErr eErr = OGRLayer::SetIgnoredFields(papszFields);
    2491         260 :     int nBatchColumns = 0;
    2492         260 :     if (!m_bHasMissingMappingToParquet && eErr == OGRERR_NONE)
    2493             :     {
    2494         260 :         m_bIgnoredFields = papszFields != nullptr && papszFields[0] != nullptr;
    2495         260 :         if (m_bIgnoredFields)
    2496             :         {
    2497         197 :             if (m_iFIDParquetColumn >= 0)
    2498             :             {
    2499           6 :                 m_nRequestedFIDColumn = nBatchColumns;
    2500           6 :                 nBatchColumns++;
    2501           6 :                 m_anRequestedParquetColumns.push_back(m_iFIDParquetColumn);
    2502             :             }
    2503             : 
    2504        5971 :             for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
    2505             :             {
    2506             :                 const auto eArrowType =
    2507        5774 :                     m_poSchema->fields()[m_anMapFieldIndexToArrowColumn[i][0]]
    2508        5774 :                         ->type()
    2509        5774 :                         ->id();
    2510        5774 :                 if (eArrowType == arrow::Type::STRUCT)
    2511             :                 {
    2512             :                     // For a struct, for the sake of simplicity in
    2513             :                     // GetNextRawFeature(), as soon as one of the member if
    2514             :                     // requested, request all Parquet columns, so that the Arrow
    2515             :                     // type doesn't change
    2516          69 :                     bool bFoundNotIgnored = false;
    2517         296 :                     for (int j = i; j < m_poFeatureDefn->GetFieldCount() &&
    2518         294 :                                     m_anMapFieldIndexToArrowColumn[i][0] ==
    2519         147 :                                         m_anMapFieldIndexToArrowColumn[j][0];
    2520             :                          ++j)
    2521             :                     {
    2522         136 :                         if (!m_poFeatureDefn->GetFieldDefn(j)->IsIgnored())
    2523             :                         {
    2524          56 :                             bFoundNotIgnored = true;
    2525          56 :                             break;
    2526             :                         }
    2527             :                     }
    2528          69 :                     if (bFoundNotIgnored)
    2529             :                     {
    2530             :                         int j;
    2531         784 :                         for (j = i; j < m_poFeatureDefn->GetFieldCount() &&
    2532         784 :                                     m_anMapFieldIndexToArrowColumn[i][0] ==
    2533         392 :                                         m_anMapFieldIndexToArrowColumn[j][0];
    2534             :                              ++j)
    2535             :                         {
    2536         336 :                             if (!m_poFeatureDefn->GetFieldDefn(j)->IsIgnored())
    2537             :                             {
    2538         330 :                                 m_anMapFieldIndexToArrayIndex.push_back(
    2539             :                                     nBatchColumns);
    2540             :                             }
    2541             :                             else
    2542             :                             {
    2543           6 :                                 m_anMapFieldIndexToArrayIndex.push_back(-1);
    2544             :                             }
    2545             : 
    2546             :                             const int iArrowCol =
    2547         336 :                                 m_anMapFieldIndexToArrowColumn[i][0];
    2548             :                             const std::string osArrowColName =
    2549         672 :                                 m_poSchema->fields()[iArrowCol]->name();
    2550             :                             const auto anParquetColsForField =
    2551             :                                 GetParquetColumnIndicesForArrowField(
    2552         672 :                                     osArrowColName.c_str());
    2553             :                             m_anRequestedParquetColumns.insert(
    2554         336 :                                 m_anRequestedParquetColumns.end(),
    2555             :                                 anParquetColsForField.begin(),
    2556         672 :                                 anParquetColsForField.end());
    2557             :                         }
    2558          56 :                         i = j - 1;
    2559          56 :                         nBatchColumns++;
    2560             :                     }
    2561             :                     else
    2562             :                     {
    2563             :                         int j;
    2564         172 :                         for (j = i; j < m_poFeatureDefn->GetFieldCount() &&
    2565         170 :                                     m_anMapFieldIndexToArrowColumn[i][0] ==
    2566          85 :                                         m_anMapFieldIndexToArrowColumn[j][0];
    2567             :                              ++j)
    2568             :                         {
    2569          74 :                             m_anMapFieldIndexToArrayIndex.push_back(-1);
    2570             :                         }
    2571          13 :                         i = j - 1;
    2572             :                     }
    2573             :                 }
    2574        5705 :                 else if (!m_poFeatureDefn->GetFieldDefn(i)->IsIgnored())
    2575             :                 {
    2576        4183 :                     m_anMapFieldIndexToArrayIndex.push_back(nBatchColumns);
    2577        4183 :                     nBatchColumns++;
    2578        4183 :                     const int iArrowCol = m_anMapFieldIndexToArrowColumn[i][0];
    2579             :                     const std::string osArrowColName =
    2580        8366 :                         m_poSchema->fields()[iArrowCol]->name();
    2581             :                     const auto anParquetColsForField =
    2582        4183 :                         GetParquetColumnIndicesForArrowField(osArrowColName);
    2583             :                     m_anRequestedParquetColumns.insert(
    2584        4183 :                         m_anRequestedParquetColumns.end(),
    2585             :                         anParquetColsForField.begin(),
    2586        8366 :                         anParquetColsForField.end());
    2587             :                 }
    2588             :                 else
    2589             :                 {
    2590        1522 :                     m_anMapFieldIndexToArrayIndex.push_back(-1);
    2591             :                 }
    2592             :             }
    2593             : 
    2594         197 :             CPLAssert(static_cast<int>(m_anMapFieldIndexToArrayIndex.size()) ==
    2595             :                       m_poFeatureDefn->GetFieldCount());
    2596             : 
    2597         406 :             for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i)
    2598             :             {
    2599         209 :                 if (!m_poFeatureDefn->GetGeomFieldDefn(i)->IsIgnored())
    2600             :                 {
    2601             :                     const auto &anVals =
    2602         185 :                         m_anMapGeomFieldIndexToParquetColumns[i];
    2603         185 :                     CPLAssert(!anVals.empty() && anVals[0] >= 0);
    2604             :                     m_anRequestedParquetColumns.insert(
    2605         185 :                         m_anRequestedParquetColumns.end(), anVals.begin(),
    2606         370 :                         anVals.end());
    2607         185 :                     m_anMapGeomFieldIndexToArrayIndex.push_back(nBatchColumns);
    2608         185 :                     nBatchColumns++;
    2609             : 
    2610         185 :                     auto oIter = m_oMapGeomFieldIndexToGeomColBBOX.find(i);
    2611             :                     const auto oIterParquet =
    2612         185 :                         m_oMapGeomFieldIndexToGeomColBBOXParquet.find(i);
    2613         275 :                     if (oIter != m_oMapGeomFieldIndexToGeomColBBOX.end() &&
    2614          90 :                         oIterParquet !=
    2615         275 :                             m_oMapGeomFieldIndexToGeomColBBOXParquet.end())
    2616             :                     {
    2617          90 :                         oIter->second.iArrayIdx = nBatchColumns++;
    2618             :                         m_anRequestedParquetColumns.insert(
    2619          90 :                             m_anRequestedParquetColumns.end(),
    2620          90 :                             oIterParquet->second.anParquetCols.begin(),
    2621         270 :                             oIterParquet->second.anParquetCols.end());
    2622             :                     }
    2623             :                 }
    2624             :                 else
    2625             :                 {
    2626          24 :                     m_anMapGeomFieldIndexToArrayIndex.push_back(-1);
    2627             :                 }
    2628             :             }
    2629             : 
    2630         197 :             CPLAssert(
    2631             :                 static_cast<int>(m_anMapGeomFieldIndexToArrayIndex.size()) ==
    2632             :                 m_poFeatureDefn->GetGeomFieldCount());
    2633             :         }
    2634             :     }
    2635             : 
    2636         260 :     m_nExpectedBatchColumns = m_bIgnoredFields ? nBatchColumns : -1;
    2637             : 
    2638         260 :     ComputeConstraintsArrayIdx();
    2639             : 
    2640             :     // Full invalidation
    2641         260 :     InvalidateCachedBatches();
    2642             : 
    2643         260 :     return eErr;
    2644             : }
    2645             : 
    2646             : /************************************************************************/
    2647             : /*                          GetFeatureCount()                           */
    2648             : /************************************************************************/
    2649             : 
    2650        1057 : GIntBig OGRParquetLayer::GetFeatureCount(int bForce)
    2651             : {
    2652        1057 :     if (m_poAttrQuery == nullptr && m_poFilterGeom == nullptr)
    2653             :     {
    2654          55 :         auto metadata = m_poArrowReader->parquet_reader()->metadata();
    2655          55 :         if (metadata)
    2656          55 :             return metadata->num_rows();
    2657             :     }
    2658        1002 :     return OGRLayer::GetFeatureCount(bForce);
    2659             : }
    2660             : 
    2661             : /************************************************************************/
    2662             : /*                           FastGetExtent()                            */
    2663             : /************************************************************************/
    2664             : 
    2665         833 : bool OGRParquetLayer::FastGetExtent(int iGeomField, OGREnvelope *psExtent) const
    2666             : {
    2667         833 :     if (OGRParquetLayerBase::FastGetExtent(iGeomField, psExtent))
    2668         818 :         return true;
    2669             : 
    2670             :     const auto oIterToGeomColBBOX =
    2671          15 :         m_oMapGeomFieldIndexToGeomColBBOXParquet.find(iGeomField);
    2672          16 :     if (oIterToGeomColBBOX != m_oMapGeomFieldIndexToGeomColBBOXParquet.end() &&
    2673           1 :         CPLTestBool(CPLGetConfigOption("OGR_PARQUET_USE_BBOX", "YES")))
    2674             :     {
    2675           1 :         OGREnvelope sExtent;
    2676             :         OGRField sMin, sMax;
    2677           1 :         OGR_RawField_SetNull(&sMin);
    2678           1 :         OGR_RawField_SetNull(&sMax);
    2679             :         bool bFoundMin, bFoundMax;
    2680           1 :         OGRFieldType eType = OFTMaxType;
    2681           1 :         OGRFieldSubType eSubType = OFSTNone;
    2682           1 :         std::string osMinTmp, osMaxTmp;
    2683           2 :         if (GetMinMaxForParquetCol(-1, oIterToGeomColBBOX->second.iParquetXMin,
    2684             :                                    nullptr, true, sMin, bFoundMin, false, sMax,
    2685             :                                    bFoundMax, eType, eSubType, osMinTmp,
    2686           3 :                                    osMaxTmp) &&
    2687           1 :             eType == OFTReal)
    2688             :         {
    2689           1 :             sExtent.MinX = sMin.Real;
    2690             : 
    2691           1 :             if (GetMinMaxForParquetCol(
    2692           1 :                     -1, oIterToGeomColBBOX->second.iParquetYMin, nullptr, true,
    2693             :                     sMin, bFoundMin, false, sMax, bFoundMax, eType, eSubType,
    2694           3 :                     osMinTmp, osMaxTmp) &&
    2695           1 :                 eType == OFTReal)
    2696             :             {
    2697           1 :                 sExtent.MinY = sMin.Real;
    2698             : 
    2699           1 :                 if (GetMinMaxForParquetCol(
    2700           1 :                         -1, oIterToGeomColBBOX->second.iParquetXMax, nullptr,
    2701             :                         false, sMin, bFoundMin, true, sMax, bFoundMax, eType,
    2702           3 :                         eSubType, osMinTmp, osMaxTmp) &&
    2703           1 :                     eType == OFTReal)
    2704             :                 {
    2705           1 :                     sExtent.MaxX = sMax.Real;
    2706             : 
    2707           1 :                     if (GetMinMaxForParquetCol(
    2708           1 :                             -1, oIterToGeomColBBOX->second.iParquetYMax,
    2709             :                             nullptr, false, sMin, bFoundMin, true, sMax,
    2710           3 :                             bFoundMax, eType, eSubType, osMinTmp, osMaxTmp) &&
    2711           1 :                         eType == OFTReal)
    2712             :                     {
    2713           1 :                         sExtent.MaxY = sMax.Real;
    2714             : 
    2715           1 :                         CPLDebug("PARQUET",
    2716             :                                  "Using statistics of bbox.minx, bbox.miny, "
    2717             :                                  "bbox.maxx, bbox.maxy columns to get extent");
    2718           1 :                         m_oMapExtents[iGeomField] = sExtent;
    2719           1 :                         *psExtent = sExtent;
    2720           1 :                         return true;
    2721             :                     }
    2722             :                 }
    2723             :             }
    2724             :         }
    2725             :     }
    2726             : 
    2727          14 :     return false;
    2728             : }
    2729             : 
    2730             : /************************************************************************/
    2731             : /*                           TestCapability()                           */
    2732             : /************************************************************************/
    2733             : 
    2734         687 : int OGRParquetLayer::TestCapability(const char *pszCap) const
    2735             : {
    2736         687 :     if (EQUAL(pszCap, OLCFastFeatureCount))
    2737          79 :         return m_poAttrQuery == nullptr && m_poFilterGeom == nullptr;
    2738             : 
    2739         608 :     if (EQUAL(pszCap, OLCIgnoreFields))
    2740           9 :         return !m_bHasMissingMappingToParquet;
    2741             : 
    2742         599 :     if (EQUAL(pszCap, OLCFastSpatialFilter))
    2743             :     {
    2744         252 :         if (m_iGeomFieldFilter >= 0 &&
    2745         168 :             m_iGeomFieldFilter < static_cast<int>(m_aeGeomEncoding.size()) &&
    2746          84 :             OGRArrowIsGeoArrowStruct(m_aeGeomEncoding[m_iGeomFieldFilter]))
    2747             :         {
    2748          84 :             return true;
    2749             :         }
    2750             : 
    2751             : #if PARQUET_VERSION_MAJOR >= 21
    2752             :         if (m_iGeomFieldFilter >= 0 &&
    2753             :             m_iGeomFieldFilter < static_cast<int>(m_aeGeomEncoding.size()) &&
    2754             :             m_aeGeomEncoding[m_iGeomFieldFilter] == OGRArrowGeomEncoding::WKB &&
    2755             :             m_iGeomFieldFilter <
    2756             :                 static_cast<int>(
    2757             :                     m_anMapGeomFieldIndexToParquetColumns.size()) &&
    2758             :             m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter].size() ==
    2759             :                 1)
    2760             :         {
    2761             :             const int iParquetCol =
    2762             :                 m_anMapGeomFieldIndexToParquetColumns[m_iGeomFieldFilter][0];
    2763             :             if (iParquetCol >= 0)
    2764             :             {
    2765             :                 const auto metadata =
    2766             :                     m_poArrowReader->parquet_reader()->metadata();
    2767             : 
    2768             :                 int nCountRowGroupsStatsValid = 0;
    2769             :                 const int nNumGroups = m_poArrowReader->num_row_groups();
    2770             :                 for (int iRowGroup = 0; iRowGroup < nNumGroups &&
    2771             :                                         nCountRowGroupsStatsValid == iRowGroup;
    2772             :                      ++iRowGroup)
    2773             :                 {
    2774             :                     const auto columnChunk =
    2775             :                         metadata->RowGroup(iRowGroup)->ColumnChunk(iParquetCol);
    2776             :                     if (auto geostats = columnChunk->geo_statistics())
    2777             :                     {
    2778             :                         if (geostats->dimension_valid()[0] &&
    2779             :                             geostats->dimension_valid()[1])
    2780             :                         {
    2781             :                             const double dfMinX = geostats->lower_bound()[0];
    2782             :                             const double dfMaxX = geostats->upper_bound()[0];
    2783             :                             const double dfMinY = geostats->lower_bound()[1];
    2784             :                             const double dfMaxY = geostats->upper_bound()[1];
    2785             :                             if (std::isfinite(dfMinX) &&
    2786             :                                 std::isfinite(dfMaxX) &&
    2787             :                                 std::isfinite(dfMinY) && std::isfinite(dfMaxY))
    2788             :                             {
    2789             :                                 nCountRowGroupsStatsValid++;
    2790             :                             }
    2791             :                         }
    2792             :                     }
    2793             :                 }
    2794             :                 if (nCountRowGroupsStatsValid == nNumGroups)
    2795             :                 {
    2796             :                     return true;
    2797             :                 }
    2798             :             }
    2799             :         }
    2800             : #endif
    2801             : 
    2802             :         // fallback to base method
    2803             :     }
    2804             : 
    2805         515 :     return OGRParquetLayerBase::TestCapability(pszCap);
    2806             : }
    2807             : 
    2808             : /************************************************************************/
    2809             : /*                          GetMetadataItem()                           */
    2810             : /************************************************************************/
    2811             : 
    2812         504 : const char *OGRParquetLayer::GetMetadataItem(const char *pszName,
    2813             :                                              const char *pszDomain)
    2814             : {
    2815             :     // Mostly for unit test purposes
    2816         504 :     if (pszDomain != nullptr && EQUAL(pszDomain, "_PARQUET_"))
    2817             :     {
    2818          11 :         int nRowGroupIdx = -1;
    2819          11 :         int nColumn = -1;
    2820          11 :         if (EQUAL(pszName, "NUM_ROW_GROUPS"))
    2821             :         {
    2822           3 :             return CPLSPrintf("%d", m_poArrowReader->num_row_groups());
    2823             :         }
    2824           8 :         if (EQUAL(pszName, "CREATOR"))
    2825             :         {
    2826           4 :             return CPLSPrintf("%s", m_poArrowReader->parquet_reader()
    2827           4 :                                         ->metadata()
    2828           2 :                                         ->created_by()
    2829           2 :                                         .c_str());
    2830             :         }
    2831          12 :         else if (sscanf(pszName, "ROW_GROUPS[%d]", &nRowGroupIdx) == 1 &&
    2832           6 :                  strstr(pszName, ".NUM_ROWS"))
    2833             :         {
    2834             :             try
    2835             :             {
    2836             :                 auto poRowGroup =
    2837           6 :                     m_poArrowReader->parquet_reader()->RowGroup(nRowGroupIdx);
    2838           3 :                 if (poRowGroup == nullptr)
    2839           0 :                     return nullptr;
    2840           3 :                 return CPLSPrintf("%" PRId64,
    2841           3 :                                   poRowGroup->metadata()->num_rows());
    2842             :             }
    2843           0 :             catch (const std::exception &)
    2844             :             {
    2845             :             }
    2846             :         }
    2847           6 :         else if (sscanf(pszName, "ROW_GROUPS[%d].COLUMNS[%d]", &nRowGroupIdx,
    2848           6 :                         &nColumn) == 2 &&
    2849           3 :                  strstr(pszName, ".COMPRESSION"))
    2850             :         {
    2851             :             try
    2852             :             {
    2853             :                 auto poRowGroup =
    2854           6 :                     m_poArrowReader->parquet_reader()->RowGroup(nRowGroupIdx);
    2855           3 :                 if (poRowGroup == nullptr)
    2856           0 :                     return nullptr;
    2857           6 :                 auto poColumn = poRowGroup->metadata()->ColumnChunk(nColumn);
    2858           3 :                 return CPLSPrintf("%s", arrow::util::Codec::GetCodecAsString(
    2859           3 :                                             poColumn->compression())
    2860           3 :                                             .c_str());
    2861             :             }
    2862           0 :             catch (const std::exception &)
    2863             :             {
    2864             :             }
    2865             :         }
    2866           0 :         return nullptr;
    2867             :     }
    2868         493 :     if (pszDomain != nullptr && EQUAL(pszDomain, "_PARQUET_METADATA_"))
    2869             :     {
    2870         628 :         const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    2871         314 :         const auto &kv_metadata = metadata->key_value_metadata();
    2872         314 :         if (kv_metadata && kv_metadata->Contains(pszName))
    2873             :         {
    2874         311 :             auto metadataItem = kv_metadata->Get(pszName);
    2875         311 :             if (metadataItem.ok())
    2876             :             {
    2877         311 :                 return CPLSPrintf("%s", metadataItem->c_str());
    2878             :             }
    2879             :         }
    2880           3 :         return nullptr;
    2881             :     }
    2882         179 :     return OGRLayer::GetMetadataItem(pszName, pszDomain);
    2883             : }
    2884             : 
    2885             : /************************************************************************/
    2886             : /*                            GetMetadata()                             */
    2887             : /************************************************************************/
    2888             : 
    2889          61 : CSLConstList OGRParquetLayer::GetMetadata(const char *pszDomain)
    2890             : {
    2891             :     // Mostly for unit test purposes
    2892          61 :     if (pszDomain != nullptr && EQUAL(pszDomain, "_PARQUET_METADATA_"))
    2893             :     {
    2894           2 :         m_aosFeatherMetadata.Clear();
    2895           4 :         const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    2896           2 :         const auto &kv_metadata = metadata->key_value_metadata();
    2897           2 :         if (kv_metadata)
    2898             :         {
    2899           8 :             for (const auto &kv : kv_metadata->sorted_pairs())
    2900             :             {
    2901             :                 m_aosFeatherMetadata.SetNameValue(kv.first.c_str(),
    2902           6 :                                                   kv.second.c_str());
    2903             :             }
    2904             :         }
    2905           2 :         return m_aosFeatherMetadata.List();
    2906             :     }
    2907             : 
    2908             :     // Mostly for unit test purposes
    2909          59 :     if (pszDomain != nullptr && EQUAL(pszDomain, "_GDAL_CREATION_OPTIONS_"))
    2910             :     {
    2911           6 :         return m_aosCreationOptions.List();
    2912             :     }
    2913             : 
    2914          53 :     return OGRLayer::GetMetadata(pszDomain);
    2915             : }
    2916             : 
    2917             : /************************************************************************/
    2918             : /*                           GetArrowStream()                           */
    2919             : /************************************************************************/
    2920             : 
    2921         135 : bool OGRParquetLayer::GetArrowStream(struct ArrowArrayStream *out_stream,
    2922             :                                      CSLConstList papszOptions)
    2923             : {
    2924             :     const char *pszMaxFeaturesInBatch =
    2925         135 :         CSLFetchNameValue(papszOptions, "MAX_FEATURES_IN_BATCH");
    2926         135 :     if (pszMaxFeaturesInBatch)
    2927             :     {
    2928          14 :         int nMaxBatchSize = atoi(pszMaxFeaturesInBatch);
    2929          14 :         if (nMaxBatchSize <= 0)
    2930           0 :             nMaxBatchSize = 1;
    2931          14 :         if (nMaxBatchSize > INT_MAX - 1)
    2932           0 :             nMaxBatchSize = INT_MAX - 1;
    2933          14 :         m_poArrowReader->set_batch_size(nMaxBatchSize);
    2934             :     }
    2935         135 :     return OGRArrowLayer::GetArrowStream(out_stream, papszOptions);
    2936             : }
    2937             : 
    2938             : /************************************************************************/
    2939             : /*                           SetNextByIndex()                           */
    2940             : /************************************************************************/
    2941             : 
    2942          14 : OGRErr OGRParquetLayer::SetNextByIndex(GIntBig nIndex)
    2943             : {
    2944          14 :     if (nIndex < 0)
    2945             :     {
    2946           4 :         m_bEOF = true;
    2947           4 :         return OGRERR_NON_EXISTING_FEATURE;
    2948             :     }
    2949             : 
    2950          20 :     const auto metadata = m_poArrowReader->parquet_reader()->metadata();
    2951          10 :     if (nIndex >= metadata->num_rows())
    2952             :     {
    2953           4 :         m_bEOF = true;
    2954           4 :         return OGRERR_NON_EXISTING_FEATURE;
    2955             :     }
    2956             : 
    2957           6 :     m_bEOF = false;
    2958             : 
    2959           6 :     if (m_bSingleBatch)
    2960             :     {
    2961           0 :         ResetReading();
    2962           0 :         m_nIdxInBatch = nIndex;
    2963           0 :         m_nFeatureIdx = nIndex;
    2964           0 :         return OGRERR_NONE;
    2965             :     }
    2966             : 
    2967           6 :     const int nNumGroups = m_poArrowReader->num_row_groups();
    2968           6 :     int64_t nAccRows = 0;
    2969           6 :     const auto nBatchSize = m_poArrowReader->properties().batch_size();
    2970           6 :     m_iRecordBatch = -1;
    2971           6 :     ResetReading();
    2972           6 :     m_iRecordBatch = 0;
    2973           7 :     for (int iGroup = 0; iGroup < nNumGroups; ++iGroup)
    2974             :     {
    2975           7 :         const auto nRowsInRowGroup = metadata->RowGroup(iGroup)->num_rows();
    2976           7 :         const int64_t nNextAccRows = nAccRows + nRowsInRowGroup;
    2977           7 :         if (nIndex < nNextAccRows)
    2978             :         {
    2979           6 :             if (!CreateRecordBatchReader(iGroup))
    2980           0 :                 return OGRERR_FAILURE;
    2981             : 
    2982          12 :             std::shared_ptr<arrow::RecordBatch> poBatch;
    2983             :             while (true)
    2984             :             {
    2985           6 :                 auto status = m_poRecordBatchReader->ReadNext(&poBatch);
    2986           6 :                 if (!status.ok())
    2987             :                 {
    2988           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    2989           0 :                              "ReadNext() failed: %s", status.message().c_str());
    2990           0 :                     m_iRecordBatch = -1;
    2991           0 :                     ResetReading();
    2992           0 :                     return OGRERR_FAILURE;
    2993             :                 }
    2994           6 :                 if (poBatch == nullptr)
    2995             :                 {
    2996           0 :                     m_iRecordBatch = -1;
    2997           0 :                     ResetReading();
    2998           0 :                     return OGRERR_FAILURE;
    2999             :                 }
    3000           6 :                 if (nIndex < nAccRows + poBatch->num_rows())
    3001             :                 {
    3002           6 :                     break;
    3003             :                 }
    3004           0 :                 nAccRows += poBatch->num_rows();
    3005           0 :                 m_iRecordBatch++;
    3006           0 :             }
    3007           6 :             m_nIdxInBatch = nIndex - nAccRows;
    3008           6 :             m_nFeatureIdx = nIndex;
    3009           6 :             SetBatch(poBatch);
    3010           6 :             return OGRERR_NONE;
    3011             :         }
    3012           1 :         nAccRows = nNextAccRows;
    3013           1 :         m_iRecordBatch +=
    3014           1 :             static_cast<int>(cpl::div_round_up(nRowsInRowGroup, nBatchSize));
    3015             :     }
    3016             : 
    3017           0 :     m_iRecordBatch = -1;
    3018           0 :     ResetReading();
    3019           0 :     return OGRERR_FAILURE;
    3020             : }
    3021             : 
    3022             : /************************************************************************/
    3023             : /*                              GetStats()                              */
    3024             : /************************************************************************/
    3025             : 
    3026             : template <class STAT_TYPE> struct GetStats
    3027             : {
    3028             :     using T = typename STAT_TYPE::T;
    3029             : 
    3030         609 :     static T min(const std::shared_ptr<parquet::FileMetaData> &metadata,
    3031             :                  const int iRowGroup, const int numRowGroups, const int iCol,
    3032             :                  bool &bFound)
    3033             :     {
    3034         609 :         T v{};
    3035         609 :         bFound = false;
    3036        1226 :         for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
    3037             :         {
    3038         653 :             const auto columnChunk =
    3039          30 :                 metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
    3040             :                     ->ColumnChunk(iCol);
    3041         623 :             const auto colStats = columnChunk->statistics();
    3042        1243 :             if (columnChunk->is_stats_set() && colStats &&
    3043         620 :                 colStats->HasMinMax())
    3044             :             {
    3045         614 :                 auto castStats = static_cast<STAT_TYPE *>(colStats.get());
    3046         614 :                 const auto rowGroupVal = castStats->min();
    3047         614 :                 if (i == 0 || rowGroupVal < v)
    3048             :                 {
    3049         602 :                     bFound = true;
    3050         602 :                     v = rowGroupVal;
    3051             :                 }
    3052             :             }
    3053           9 :             else if (columnChunk->num_values() > 0)
    3054             :             {
    3055           6 :                 bFound = false;
    3056           6 :                 break;
    3057             :             }
    3058             :         }
    3059         609 :         return v;
    3060             :     }
    3061             : 
    3062         598 :     static T max(const std::shared_ptr<parquet::FileMetaData> &metadata,
    3063             :                  const int iRowGroup, const int numRowGroups, const int iCol,
    3064             :                  bool &bFound)
    3065             :     {
    3066         598 :         T v{};
    3067         598 :         bFound = false;
    3068        1210 :         for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
    3069             :         {
    3070         642 :             const auto columnChunk =
    3071          30 :                 metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
    3072             :                     ->ColumnChunk(iCol);
    3073         612 :             const auto colStats = columnChunk->statistics();
    3074        1222 :             if (columnChunk->is_stats_set() && colStats &&
    3075         610 :                 colStats->HasMinMax())
    3076             :             {
    3077         610 :                 auto castStats = static_cast<STAT_TYPE *>(colStats.get());
    3078         610 :                 const auto rowGroupVal = castStats->max();
    3079         610 :                 if (i == 0 || rowGroupVal > v)
    3080             :                 {
    3081         608 :                     bFound = true;
    3082         608 :                     v = rowGroupVal;
    3083             :                 }
    3084             :             }
    3085           2 :             else if (columnChunk->num_values() > 0)
    3086             :             {
    3087           0 :                 bFound = false;
    3088           0 :                 break;
    3089             :             }
    3090             :         }
    3091         598 :         return v;
    3092             :     }
    3093             : };
    3094             : 
    3095             : template <> struct GetStats<parquet::ByteArrayStatistics>
    3096             : {
    3097             :     static std::string
    3098          39 :     min(const std::shared_ptr<parquet::FileMetaData> &metadata,
    3099             :         const int iRowGroup, const int numRowGroups, const int iCol,
    3100             :         bool &bFound)
    3101             :     {
    3102          39 :         std::string v{};
    3103          39 :         bFound = false;
    3104          79 :         for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
    3105             :         {
    3106             :             const auto columnChunk =
    3107          40 :                 metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
    3108          80 :                     ->ColumnChunk(iCol);
    3109          80 :             const auto colStats = columnChunk->statistics();
    3110          80 :             if (columnChunk->is_stats_set() && colStats &&
    3111          40 :                 colStats->HasMinMax())
    3112             :             {
    3113             :                 auto castStats =
    3114          40 :                     static_cast<parquet::ByteArrayStatistics *>(colStats.get());
    3115          40 :                 const auto rowGroupValRaw = castStats->min();
    3116             :                 std::string rowGroupVal(
    3117          40 :                     reinterpret_cast<const char *>(rowGroupValRaw.ptr),
    3118          80 :                     rowGroupValRaw.len);
    3119          40 :                 if (i == 0 || rowGroupVal < v)
    3120             :                 {
    3121          39 :                     bFound = true;
    3122          39 :                     v = std::move(rowGroupVal);
    3123             :                 }
    3124             :             }
    3125             :         }
    3126          39 :         return v;
    3127             :     }
    3128             : 
    3129             :     static std::string
    3130          39 :     max(const std::shared_ptr<parquet::FileMetaData> &metadata,
    3131             :         const int iRowGroup, const int numRowGroups, const int iCol,
    3132             :         bool &bFound)
    3133             :     {
    3134          39 :         std::string v{};
    3135          39 :         bFound = false;
    3136          79 :         for (int i = 0; i < (iRowGroup < 0 ? numRowGroups : 1); i++)
    3137             :         {
    3138             :             const auto columnChunk =
    3139          40 :                 metadata->RowGroup(iRowGroup < 0 ? i : iRowGroup)
    3140          40 :                     ->ColumnChunk(iCol);
    3141          40 :             const auto colStats = columnChunk->statistics();
    3142          80 :             if (columnChunk->is_stats_set() && colStats &&
    3143          40 :                 colStats->HasMinMax())
    3144             :             {
    3145             :                 auto castStats =
    3146          40 :                     static_cast<parquet::ByteArrayStatistics *>(colStats.get());
    3147          40 :                 const auto rowGroupValRaw = castStats->max();
    3148             :                 std::string rowGroupVal(
    3149          40 :                     reinterpret_cast<const char *>(rowGroupValRaw.ptr),
    3150          80 :                     rowGroupValRaw.len);
    3151          40 :                 if (i == 0 || rowGroupVal > v)
    3152             :                 {
    3153          40 :                     bFound = true;
    3154          40 :                     v = std::move(rowGroupVal);
    3155             :                 }
    3156             :             }
    3157             :             else
    3158             :             {
    3159           0 :                 bFound = false;
    3160           0 :                 break;
    3161             :             }
    3162             :         }
    3163          39 :         return v;
    3164             :     }
    3165             : };
    3166             : 
    3167             : /************************************************************************/
    3168             : /*                        GetMinMaxForOGRField()                        */
    3169             : /************************************************************************/
    3170             : 
    3171         256 : bool OGRParquetLayer::GetMinMaxForOGRField(int iRowGroup,  // -1 for all
    3172             :                                            int iOGRField, bool bComputeMin,
    3173             :                                            OGRField &sMin, bool &bFoundMin,
    3174             :                                            bool bComputeMax, OGRField &sMax,
    3175             :                                            bool &bFoundMax, OGRFieldType &eType,
    3176             :                                            OGRFieldSubType &eSubType,
    3177             :                                            std::string &osMinTmp,
    3178             :                                            std::string &osMaxTmp) const
    3179             : {
    3180         256 :     OGR_RawField_SetNull(&sMin);
    3181         256 :     OGR_RawField_SetNull(&sMax);
    3182         256 :     eType = OFTReal;
    3183         256 :     eSubType = OFSTNone;
    3184         256 :     bFoundMin = false;
    3185         256 :     bFoundMax = false;
    3186             : 
    3187             :     const std::vector<int> anCols =
    3188             :         iOGRField == OGR_FID_INDEX
    3189           5 :             ? std::vector<int>{m_iFIDParquetColumn}
    3190             :             : GetParquetColumnIndicesForArrowField(
    3191        1019 :                   GetLayerDefn()->GetFieldDefn(iOGRField)->GetNameRef());
    3192         256 :     if (anCols.empty() || anCols[0] < 0)
    3193           2 :         return false;
    3194         254 :     const int iCol = anCols[0];
    3195             :     const auto &arrowType = iOGRField == OGR_FID_INDEX
    3196         254 :                                 ? m_poFIDType
    3197         249 :                                 : GetArrowFieldTypes()[iOGRField];
    3198             : 
    3199         254 :     const bool bRet = GetMinMaxForParquetCol(
    3200             :         iRowGroup, iCol, arrowType, bComputeMin, sMin, bFoundMin, bComputeMax,
    3201             :         sMax, bFoundMax, eType, eSubType, osMinTmp, osMaxTmp);
    3202             : 
    3203         254 :     if (eType == OFTInteger64 && arrowType->id() == arrow::Type::TIMESTAMP)
    3204             :     {
    3205             :         const OGRFieldDefn oDummyFIDFieldDefn(m_osFIDColumn.c_str(),
    3206           4 :                                               OFTInteger64);
    3207             :         const OGRFieldDefn *poFieldDefn =
    3208           2 :             iOGRField == OGR_FID_INDEX ? &oDummyFIDFieldDefn
    3209             :                                        : const_cast<OGRParquetLayer *>(this)
    3210           2 :                                              ->GetLayerDefn()
    3211           2 :                                              ->GetFieldDefn(iOGRField);
    3212           2 :         if (poFieldDefn->GetType() == OFTDateTime)
    3213             :         {
    3214             :             const auto timestampType =
    3215           2 :                 static_cast<arrow::TimestampType *>(arrowType.get());
    3216           2 :             if (bFoundMin)
    3217             :             {
    3218           1 :                 const int64_t timestamp = sMin.Integer64;
    3219           1 :                 OGRArrowLayer::TimestampToOGR(timestamp, timestampType,
    3220             :                                               poFieldDefn->GetTZFlag(), &sMin);
    3221             :             }
    3222           2 :             if (bFoundMax)
    3223             :             {
    3224           1 :                 const int64_t timestamp = sMax.Integer64;
    3225           1 :                 OGRArrowLayer::TimestampToOGR(timestamp, timestampType,
    3226             :                                               poFieldDefn->GetTZFlag(), &sMax);
    3227             :             }
    3228           2 :             eType = OFTDateTime;
    3229             :         }
    3230             :     }
    3231             : 
    3232         254 :     return bRet;
    3233             : }
    3234             : 
    3235             : /************************************************************************/
    3236             : /*                       GetMinMaxForParquetCol()                       */
    3237             : /************************************************************************/
    3238             : 
    3239        1067 : bool OGRParquetLayer::GetMinMaxForParquetCol(
    3240             :     int iRowGroup,  // -1 for all
    3241             :     int iCol,
    3242             :     const std::shared_ptr<arrow::DataType> &arrowType,  // potentially nullptr
    3243             :     bool bComputeMin, OGRField &sMin, bool &bFoundMin, bool bComputeMax,
    3244             :     OGRField &sMax, bool &bFoundMax, OGRFieldType &eType,
    3245             :     OGRFieldSubType &eSubType, std::string &osMinTmp,
    3246             :     std::string &osMaxTmp) const
    3247             : {
    3248        1067 :     OGR_RawField_SetNull(&sMin);
    3249        1067 :     OGR_RawField_SetNull(&sMax);
    3250        1067 :     eType = OFTReal;
    3251        1067 :     eSubType = OFSTNone;
    3252        1067 :     bFoundMin = false;
    3253        1067 :     bFoundMax = false;
    3254             : 
    3255        2134 :     const auto metadata = GetReader()->parquet_reader()->metadata();
    3256        1067 :     const auto numRowGroups = metadata->num_row_groups();
    3257             : 
    3258        1067 :     if (numRowGroups == 0)
    3259           0 :         return false;
    3260             : 
    3261        2134 :     const auto rowGroup0 = metadata->RowGroup(0);
    3262        1067 :     if (iCol < 0 || iCol >= rowGroup0->num_columns())
    3263             :     {
    3264           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3265             :                  "GetMinMaxForParquetCol(): invalid iCol=%d", iCol);
    3266           0 :         return false;
    3267             :     }
    3268        2134 :     const auto rowGroup0columnChunk = rowGroup0->ColumnChunk(iCol);
    3269        2134 :     const auto rowGroup0Stats = rowGroup0columnChunk->statistics();
    3270        1067 :     if (!(rowGroup0columnChunk->is_stats_set() && rowGroup0Stats))
    3271             :     {
    3272           0 :         CPLDebug("PARQUET", "Statistics not available for field %s",
    3273           0 :                  rowGroup0columnChunk->path_in_schema()->ToDotString().c_str());
    3274           0 :         return false;
    3275             :     }
    3276             : 
    3277        1067 :     const auto physicalType = rowGroup0Stats->physical_type();
    3278             : 
    3279        1067 :     if (bComputeMin)
    3280             :     {
    3281         651 :         if (physicalType == parquet::Type::BOOLEAN)
    3282             :         {
    3283          54 :             eType = OFTInteger;
    3284          54 :             eSubType = OFSTBoolean;
    3285          54 :             sMin.Integer = GetStats<parquet::BoolStatistics>::min(
    3286             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    3287             :         }
    3288         597 :         else if (physicalType == parquet::Type::INT32)
    3289             :         {
    3290          78 :             if (arrowType && arrowType->id() == arrow::Type::UINT32)
    3291             :             {
    3292             :                 // With parquet file version 2.0,
    3293             :                 // statistics of uint32 fields are
    3294             :                 // stored as signed int32 values...
    3295           1 :                 eType = OFTInteger64;
    3296           1 :                 int nVal = GetStats<parquet::Int32Statistics>::min(
    3297             :                     metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    3298           1 :                 if (bFoundMin)
    3299             :                 {
    3300           1 :                     sMin.Integer64 = static_cast<uint32_t>(nVal);
    3301             :                 }
    3302             :             }
    3303             :             else
    3304             :             {
    3305          77 :                 eType = OFTInteger;
    3306          77 :                 if (arrowType && arrowType->id() == arrow::Type::INT16)
    3307           1 :                     eSubType = OFSTInt16;
    3308          77 :                 sMin.Integer = GetStats<parquet::Int32Statistics>::min(
    3309             :                     metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    3310             :             }
    3311             :         }
    3312         519 :         else if (physicalType == parquet::Type::INT64)
    3313             :         {
    3314          37 :             eType = OFTInteger64;
    3315          37 :             sMin.Integer64 = GetStats<parquet::Int64Statistics>::min(
    3316             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    3317             :         }
    3318         482 :         else if (physicalType == parquet::Type::FLOAT)
    3319             :         {
    3320         138 :             eType = OFTReal;
    3321         138 :             eSubType = OFSTFloat32;
    3322         138 :             sMin.Real = GetStats<parquet::FloatStatistics>::min(
    3323             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    3324             :         }
    3325         344 :         else if (physicalType == parquet::Type::DOUBLE)
    3326             :         {
    3327         302 :             eType = OFTReal;
    3328         302 :             sMin.Real = GetStats<parquet::DoubleStatistics>::min(
    3329             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    3330             :         }
    3331          42 :         else if (arrowType &&
    3332          53 :                  (arrowType->id() == arrow::Type::STRING ||
    3333          95 :                   arrowType->id() == arrow::Type::LARGE_STRING) &&
    3334             :                  physicalType == parquet::Type::BYTE_ARRAY)
    3335             :         {
    3336          78 :             osMinTmp = GetStats<parquet::ByteArrayStatistics>::min(
    3337          39 :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMin);
    3338          39 :             if (bFoundMin)
    3339             :             {
    3340          39 :                 eType = OFTString;
    3341          39 :                 sMin.String = &osMinTmp[0];
    3342             :             }
    3343             :         }
    3344             :     }
    3345             : 
    3346        1067 :     if (bComputeMax)
    3347             :     {
    3348         640 :         if (physicalType == parquet::Type::BOOLEAN)
    3349             :         {
    3350          54 :             eType = OFTInteger;
    3351          54 :             eSubType = OFSTBoolean;
    3352          54 :             sMax.Integer = GetStats<parquet::BoolStatistics>::max(
    3353             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    3354             :         }
    3355         586 :         else if (physicalType == parquet::Type::INT32)
    3356             :         {
    3357          78 :             if (arrowType && arrowType->id() == arrow::Type::UINT32)
    3358             :             {
    3359             :                 // With parquet file version 2.0,
    3360             :                 // statistics of uint32 fields are
    3361             :                 // stored as signed int32 values...
    3362           1 :                 eType = OFTInteger64;
    3363           1 :                 int nVal = GetStats<parquet::Int32Statistics>::max(
    3364             :                     metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    3365           1 :                 if (bFoundMax)
    3366             :                 {
    3367           1 :                     sMax.Integer64 = static_cast<uint32_t>(nVal);
    3368             :                 }
    3369             :             }
    3370             :             else
    3371             :             {
    3372          77 :                 eType = OFTInteger;
    3373          77 :                 if (arrowType && arrowType->id() == arrow::Type::INT16)
    3374           1 :                     eSubType = OFSTInt16;
    3375          77 :                 sMax.Integer = GetStats<parquet::Int32Statistics>::max(
    3376             :                     metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    3377             :             }
    3378             :         }
    3379         508 :         else if (physicalType == parquet::Type::INT64)
    3380             :         {
    3381          37 :             eType = OFTInteger64;
    3382          37 :             sMax.Integer64 = GetStats<parquet::Int64Statistics>::max(
    3383             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    3384             :         }
    3385         471 :         else if (physicalType == parquet::Type::FLOAT)
    3386             :         {
    3387         128 :             eType = OFTReal;
    3388         128 :             eSubType = OFSTFloat32;
    3389         128 :             sMax.Real = GetStats<parquet::FloatStatistics>::max(
    3390             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    3391             :         }
    3392         343 :         else if (physicalType == parquet::Type::DOUBLE)
    3393             :         {
    3394         301 :             eType = OFTReal;
    3395         301 :             sMax.Real = GetStats<parquet::DoubleStatistics>::max(
    3396             :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    3397             :         }
    3398          42 :         else if (arrowType &&
    3399          53 :                  (arrowType->id() == arrow::Type::STRING ||
    3400          95 :                   arrowType->id() == arrow::Type::LARGE_STRING) &&
    3401             :                  physicalType == parquet::Type::BYTE_ARRAY)
    3402             :         {
    3403          78 :             osMaxTmp = GetStats<parquet::ByteArrayStatistics>::max(
    3404          39 :                 metadata, iRowGroup, numRowGroups, iCol, bFoundMax);
    3405          39 :             if (bFoundMax)
    3406             :             {
    3407          39 :                 eType = OFTString;
    3408          39 :                 sMax.String = &osMaxTmp[0];
    3409             :             }
    3410             :         }
    3411             :     }
    3412             : 
    3413        1067 :     return bFoundMin || bFoundMax;
    3414             : }
    3415             : 
    3416             : /************************************************************************/
    3417             : /*                        GeomColsBBOXParquet()                         */
    3418             : /************************************************************************/
    3419             : 
    3420             : /** Return for a given geometry column (iGeom: in [0, GetGeomFieldCount()-1] range),
    3421             :  * the Parquet column number of the corresponding xmin,ymin,xmax,ymax bounding
    3422             :  * box columns, if existing.
    3423             :  */
    3424           1 : bool OGRParquetLayer::GeomColsBBOXParquet(int iGeom, int &iParquetXMin,
    3425             :                                           int &iParquetYMin, int &iParquetXMax,
    3426             :                                           int &iParquetYMax) const
    3427             : {
    3428           1 :     const auto oIter = m_oMapGeomFieldIndexToGeomColBBOXParquet.find(iGeom);
    3429             :     const bool bFound =
    3430           1 :         (oIter != m_oMapGeomFieldIndexToGeomColBBOXParquet.end());
    3431           1 :     if (bFound)
    3432             :     {
    3433           1 :         iParquetXMin = oIter->second.iParquetXMin;
    3434           1 :         iParquetYMin = oIter->second.iParquetYMin;
    3435           1 :         iParquetXMax = oIter->second.iParquetXMax;
    3436           1 :         iParquetYMax = oIter->second.iParquetYMax;
    3437             :     }
    3438           1 :     return bFound;
    3439             : }

Generated by: LCOV version 1.14