LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/openfilegdb - filegdbindex.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1069 1223 87.4 %
Date: 2025-09-10 17:48:50 Functions: 88 101 87.1 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Implements reading of FileGDB indexes
       5             :  * Author:   Even Rouault, <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_port.h"
      14             : #include "filegdbtable_priv.h"
      15             : 
      16             : #include <cmath>
      17             : #include <cstddef>
      18             : #include <cstdio>
      19             : #include <cstring>
      20             : #include <ctime>
      21             : #include <algorithm>
      22             : #include <array>
      23             : #include <memory>
      24             : #include <string>
      25             : #include <vector>
      26             : 
      27             : #include "cpl_conv.h"
      28             : #include "cpl_error.h"
      29             : #include "cpl_mem_cache.h"
      30             : #include "cpl_noncopyablevector.h"
      31             : #include "cpl_string.h"
      32             : #include "cpl_time.h"
      33             : #include "cpl_vsi.h"
      34             : #include "ogr_core.h"
      35             : #include "filegdbtable.h"
      36             : 
      37             : namespace OpenFileGDB
      38             : {
      39             : 
      40             : FileGDBIndex::~FileGDBIndex() = default;
      41             : 
      42             : /************************************************************************/
      43             : /*                    GetFieldNameFromExpression()                      */
      44             : /************************************************************************/
      45             : 
      46             : std::string
      47        2042 : FileGDBIndex::GetFieldNameFromExpression(const std::string &osExpression)
      48             : {
      49        2073 :     if (STARTS_WITH_CI(osExpression.c_str(), "LOWER(") &&
      50          31 :         osExpression.back() == ')')
      51             :         return osExpression.substr(strlen("LOWER("),
      52          31 :                                    osExpression.size() - strlen("LOWER()"));
      53        2011 :     return osExpression;
      54             : }
      55             : 
      56             : /************************************************************************/
      57             : /*                           GetFieldName()                             */
      58             : /************************************************************************/
      59             : 
      60        1465 : std::string FileGDBIndex::GetFieldName() const
      61             : {
      62        1465 :     return GetFieldNameFromExpression(m_osExpression);
      63             : }
      64             : 
      65             : /************************************************************************/
      66             : /*                        FileGDBTrivialIterator                        */
      67             : /************************************************************************/
      68             : 
      69             : class FileGDBTrivialIterator final : public FileGDBIterator
      70             : {
      71             :     FileGDBIterator *poParentIter = nullptr;
      72             :     FileGDBTable *poTable = nullptr;
      73             :     int64_t iRow = 0;
      74             : 
      75             :     FileGDBTrivialIterator(const FileGDBTrivialIterator &) = delete;
      76             :     FileGDBTrivialIterator &operator=(const FileGDBTrivialIterator &) = delete;
      77             : 
      78             :   public:
      79             :     explicit FileGDBTrivialIterator(FileGDBIterator *poParentIter);
      80             : 
      81         144 :     ~FileGDBTrivialIterator() override
      82          72 :     {
      83          72 :         delete poParentIter;
      84         144 :     }
      85             : 
      86           3 :     FileGDBTable *GetTable() override
      87             :     {
      88           3 :         return poTable;
      89             :     }
      90             : 
      91         150 :     void Reset() override
      92             :     {
      93         150 :         iRow = 0;
      94         150 :         poParentIter->Reset();
      95         150 :     }
      96             : 
      97             :     int64_t GetNextRowSortedByFID() override;
      98             : 
      99          40 :     int64_t GetRowCount() override
     100             :     {
     101          40 :         return poTable->GetTotalRecordCount();
     102             :     }
     103             : 
     104         698 :     int64_t GetNextRowSortedByValue() override
     105             :     {
     106         698 :         return poParentIter->GetNextRowSortedByValue();
     107             :     }
     108             : 
     109          13 :     const OGRField *GetMinValue(int &eOutType) override
     110             :     {
     111          13 :         return poParentIter->GetMinValue(eOutType);
     112             :     }
     113             : 
     114          34 :     const OGRField *GetMaxValue(int &eOutType) override
     115             :     {
     116          34 :         return poParentIter->GetMaxValue(eOutType);
     117             :     }
     118             : 
     119           7 :     virtual bool GetMinMaxSumCount(double &dfMin, double &dfMax, double &dfSum,
     120             :                                    int &nCount) override
     121             :     {
     122           7 :         return poParentIter->GetMinMaxSumCount(dfMin, dfMax, dfSum, nCount);
     123             :     }
     124             : };
     125             : 
     126             : /************************************************************************/
     127             : /*                        FileGDBNotIterator                            */
     128             : /************************************************************************/
     129             : 
     130             : class FileGDBNotIterator final : public FileGDBIterator
     131             : {
     132             :     FileGDBIterator *poIterBase = nullptr;
     133             :     FileGDBTable *poTable = nullptr;
     134             :     int64_t iRow = 0;
     135             :     int64_t iNextRowBase = -1;
     136             :     int bNoHoles = 0;
     137             : 
     138             :     FileGDBNotIterator(const FileGDBNotIterator &) = delete;
     139             :     FileGDBNotIterator &operator=(const FileGDBNotIterator &) = delete;
     140             : 
     141             :   public:
     142             :     explicit FileGDBNotIterator(FileGDBIterator *poIterBase);
     143             :     ~FileGDBNotIterator() override;
     144             : 
     145           4 :     FileGDBTable *GetTable() override
     146             :     {
     147           4 :         return poTable;
     148             :     }
     149             : 
     150             :     void Reset() override;
     151             :     int64_t GetNextRowSortedByFID() override;
     152             :     int64_t GetRowCount() override;
     153             : };
     154             : 
     155             : /************************************************************************/
     156             : /*                        FileGDBAndIterator                            */
     157             : /************************************************************************/
     158             : 
     159             : class FileGDBAndIterator final : public FileGDBIterator
     160             : {
     161             :     FileGDBIterator *poIter1 = nullptr;
     162             :     FileGDBIterator *poIter2 = nullptr;
     163             :     int64_t iNextRow1 = -1;
     164             :     int64_t iNextRow2 = -1;
     165             :     bool m_bTakeOwnershipOfIterators = false;
     166             : 
     167             :     FileGDBAndIterator(const FileGDBAndIterator &) = delete;
     168             :     FileGDBAndIterator &operator=(const FileGDBAndIterator &) = delete;
     169             : 
     170             :   public:
     171             :     FileGDBAndIterator(FileGDBIterator *poIter1, FileGDBIterator *poIter2,
     172             :                        bool bTakeOwnershipOfIterators);
     173             :     ~FileGDBAndIterator() override;
     174             : 
     175           0 :     FileGDBTable *GetTable() override
     176             :     {
     177           0 :         return poIter1->GetTable();
     178             :     }
     179             : 
     180             :     void Reset() override;
     181             :     int64_t GetNextRowSortedByFID() override;
     182             : };
     183             : 
     184             : /************************************************************************/
     185             : /*                        FileGDBOrIterator                             */
     186             : /************************************************************************/
     187             : 
     188             : class FileGDBOrIterator final : public FileGDBIterator
     189             : {
     190             :     FileGDBIterator *poIter1 = nullptr;
     191             :     FileGDBIterator *poIter2 = nullptr;
     192             :     int bIteratorAreExclusive = false;
     193             :     int64_t iNextRow1 = -1;
     194             :     int64_t iNextRow2 = -1;
     195             :     bool bHasJustReset = true;
     196             : 
     197             :     FileGDBOrIterator(const FileGDBOrIterator &) = delete;
     198             :     FileGDBOrIterator &operator=(const FileGDBOrIterator &) = delete;
     199             : 
     200             :   public:
     201             :     FileGDBOrIterator(FileGDBIterator *poIter1, FileGDBIterator *poIter2,
     202             :                       int bIteratorAreExclusive = FALSE);
     203             :     ~FileGDBOrIterator() override;
     204             : 
     205           6 :     FileGDBTable *GetTable() override
     206             :     {
     207           6 :         return poIter1->GetTable();
     208             :     }
     209             : 
     210             :     void Reset() override;
     211             :     int64_t GetNextRowSortedByFID() override;
     212             :     int64_t GetRowCount() override;
     213             : };
     214             : 
     215             : /************************************************************************/
     216             : /*                       FileGDBIndexIteratorBase                       */
     217             : /************************************************************************/
     218             : 
     219             : constexpr int MAX_DEPTH = 3;
     220             : constexpr int FGDB_PAGE_SIZE_V1 = 4096;
     221             : constexpr int FGDB_PAGE_SIZE_V2 = 65536;
     222             : constexpr int MAX_FGDB_PAGE_SIZE = FGDB_PAGE_SIZE_V2;
     223             : 
     224             : class FileGDBIndexIteratorBase /* non final */ : virtual public FileGDBIterator
     225             : {
     226             :   protected:
     227             :     FileGDBTable *poParent = nullptr;
     228             :     bool bAscending = false;
     229             :     VSILFILE *fpCurIdx = nullptr;
     230             : 
     231             :     //! Version of .atx/.spx: 1 or 2
     232             :     GUInt32 m_nVersion = 0;
     233             : 
     234             :     // Number of pages of size m_nPageSize
     235             :     GUInt32 m_nPageCount = 0;
     236             : 
     237             :     //! Page size in bytes: 4096 for v1 format, 65536 for v2
     238             :     int m_nPageSize = 0;
     239             : 
     240             :     //! Maximum number of features or sub-pages referenced by a page.
     241             :     GUInt32 nMaxPerPages = 0;
     242             : 
     243             :     //! Size of ObjectID referenced in pages, in bytes.
     244             :     // sizeof(uint32_t) for V1, sizeof(uint64_t) for V2
     245             :     GUInt32 m_nObjectIDSize = 0;
     246             : 
     247             :     //! Size of the indexed value, in bytes.
     248             :     GUInt32 m_nValueSize = 0;
     249             : 
     250             :     //! Non-leaf page header size in bytes. 8 for V1, 12 for V2
     251             :     GUInt32 m_nNonLeafPageHeaderSize = 0;
     252             : 
     253             :     //! Leaf page header size in bytes. 12 for V1, 20 for V2
     254             :     GUInt32 m_nLeafPageHeaderSize = 0;
     255             : 
     256             :     //! Offset within a page at which the first indexed value is found.
     257             :     GUInt32 m_nOffsetFirstValInPage = 0;
     258             : 
     259             :     //! Number of values referenced in the index.
     260             :     GUInt64 m_nValueCountInIdx = 0;
     261             : 
     262             :     GUInt32 nIndexDepth = 0;
     263             : #ifdef DEBUG
     264             :     uint64_t iLoadedPage[MAX_DEPTH];
     265             : #endif
     266             :     int iFirstPageIdx[MAX_DEPTH];
     267             :     int iLastPageIdx[MAX_DEPTH];
     268             :     int iCurPageIdx[MAX_DEPTH];
     269             :     GUInt32 nSubPagesCount[MAX_DEPTH];
     270             :     uint64_t nLastPageAccessed[MAX_DEPTH];
     271             : 
     272             :     int iCurFeatureInPage = -1;
     273             :     int nFeaturesInPage = 0;
     274             : 
     275             :     bool bEOF = false;
     276             : 
     277             :     GByte abyPage[MAX_DEPTH][MAX_FGDB_PAGE_SIZE];
     278             :     GByte abyPageFeature[MAX_FGDB_PAGE_SIZE];
     279             : 
     280             :     typedef lru11::Cache<uint64_t, cpl::NonCopyableVector<GByte>> CacheType;
     281             :     std::array<CacheType, MAX_DEPTH> m_oCachePage{
     282             :         {CacheType{2, 0}, CacheType{2, 0}, CacheType{2, 0}}};
     283             :     CacheType m_oCacheFeaturePage{2, 0};
     284             : 
     285             :     bool ReadTrailer(const std::string &osFilename);
     286             : 
     287             :     uint64_t ReadPageNumber(int iLevel);
     288             :     bool LoadNextPage(int iLevel);
     289             :     virtual bool FindPages(int iLevel, uint64_t nPage) = 0;
     290             :     bool LoadNextFeaturePage();
     291             : 
     292             :     FileGDBIndexIteratorBase(FileGDBTable *poParent, int bAscending);
     293             : 
     294             :     FileGDBIndexIteratorBase(const FileGDBIndexIteratorBase &) = delete;
     295             :     FileGDBIndexIteratorBase &
     296             :     operator=(const FileGDBIndexIteratorBase &) = delete;
     297             : 
     298             :   public:
     299             :     ~FileGDBIndexIteratorBase() override;
     300             : 
     301         156 :     FileGDBTable *GetTable() override
     302             :     {
     303         156 :         return poParent;
     304             :     }
     305             : 
     306             :     void Reset() override;
     307             : };
     308             : 
     309             : /************************************************************************/
     310             : /*                        FileGDBIndexIterator                          */
     311             : /************************************************************************/
     312             : 
     313             : constexpr int UUID_LEN_AS_STRING = 38;
     314             : constexpr int MAX_UTF8_LEN_STR = 4 * MAX_CAR_COUNT_INDEXED_STR;
     315             : 
     316             : class FileGDBIndexIterator final : public FileGDBIndexIteratorBase
     317             : {
     318             :     FileGDBFieldType eFieldType = FGFT_UNDEFINED;
     319             :     FileGDBSQLOp eOp = FGSO_ISNOTNULL;
     320             :     OGRField sValue{};
     321             : 
     322             :     bool bEvaluateToFALSE = false;
     323             : 
     324             :     int iSorted = 0;
     325             :     int nSortedCount = -1;
     326             :     int64_t *panSortedRows = nullptr;
     327             :     int SortRows();
     328             : 
     329             :     GUInt16 asUTF16Str[MAX_CAR_COUNT_INDEXED_STR];
     330             :     int nStrLen = 0;
     331             :     char szUUID[UUID_LEN_AS_STRING + 1];
     332             : 
     333             :     OGRField sMin{};
     334             :     OGRField sMax{};
     335             :     char szMin[MAX_UTF8_LEN_STR + 1];
     336             :     char szMax[MAX_UTF8_LEN_STR + 1];
     337             :     const OGRField *GetMinMaxValue(OGRField *psField, int &eOutType,
     338             :                                    int bIsMin);
     339             : 
     340             :     bool FindPages(int iLevel, uint64_t nPage) override;
     341             :     int64_t GetNextRow();
     342             : 
     343             :     FileGDBIndexIterator(FileGDBTable *poParent, int bAscending);
     344             :     int SetConstraint(int nFieldIdx, FileGDBSQLOp op,
     345             :                       OGRFieldType eOGRFieldType, const OGRField *psValue);
     346             : 
     347             :     template <class Getter>
     348             :     void GetMinMaxSumCount(double &dfMin, double &dfMax, double &dfSum,
     349             :                            int &nCount);
     350             : 
     351             :     FileGDBIndexIterator(const FileGDBIndexIterator &) = delete;
     352             :     FileGDBIndexIterator &operator=(const FileGDBIndexIterator &) = delete;
     353             : 
     354             :   public:
     355             :     ~FileGDBIndexIterator() override;
     356             : 
     357             :     static FileGDBIterator *Build(FileGDBTable *poParentIn, int nFieldIdx,
     358             :                                   int bAscendingIn, FileGDBSQLOp op,
     359             :                                   OGRFieldType eOGRFieldType,
     360             :                                   const OGRField *psValue);
     361             : 
     362             :     int64_t GetNextRowSortedByFID() override;
     363             :     int64_t GetRowCount() override;
     364             :     void Reset() override;
     365             : 
     366         757 :     int64_t GetNextRowSortedByValue() override
     367             :     {
     368         757 :         return GetNextRow();
     369             :     }
     370             : 
     371             :     const OGRField *GetMinValue(int &eOutType) override;
     372             :     const OGRField *GetMaxValue(int &eOutType) override;
     373             :     virtual bool GetMinMaxSumCount(double &dfMin, double &dfMax, double &dfSum,
     374             :                                    int &nCount) override;
     375             : };
     376             : 
     377             : /************************************************************************/
     378             : /*                            GetMinValue()                             */
     379             : /************************************************************************/
     380             : 
     381           0 : const OGRField *FileGDBIterator::GetMinValue(int &eOutType)
     382             : {
     383           0 :     PrintError();
     384           0 :     eOutType = -1;
     385           0 :     return nullptr;
     386             : }
     387             : 
     388             : /************************************************************************/
     389             : /*                            GetMaxValue()                             */
     390             : /************************************************************************/
     391             : 
     392           0 : const OGRField *FileGDBIterator::GetMaxValue(int &eOutType)
     393             : {
     394           0 :     PrintError();
     395           0 :     eOutType = -1;
     396           0 :     return nullptr;
     397             : }
     398             : 
     399             : /************************************************************************/
     400             : /*                       GetNextRowSortedByValue()                      */
     401             : /************************************************************************/
     402             : 
     403           0 : int64_t FileGDBIterator::GetNextRowSortedByValue()
     404             : {
     405           0 :     PrintError();
     406           0 :     return -1;
     407             : }
     408             : 
     409             : /************************************************************************/
     410             : /*                        GetMinMaxSumCount()                           */
     411             : /************************************************************************/
     412             : 
     413           0 : bool FileGDBIterator::GetMinMaxSumCount(double &dfMin, double &dfMax,
     414             :                                         double &dfSum, int &nCount)
     415             : {
     416           0 :     PrintError();
     417           0 :     dfMin = 0.0;
     418           0 :     dfMax = 0.0;
     419           0 :     dfSum = 0.0;
     420           0 :     nCount = 0;
     421           0 :     return false;
     422             : }
     423             : 
     424             : /************************************************************************/
     425             : /*                             Build()                                  */
     426             : /************************************************************************/
     427             : 
     428         404 : FileGDBIterator *FileGDBIterator::Build(FileGDBTable *poParent, int nFieldIdx,
     429             :                                         int bAscending, FileGDBSQLOp op,
     430             :                                         OGRFieldType eOGRFieldType,
     431             :                                         const OGRField *psValue)
     432             : {
     433         404 :     return FileGDBIndexIterator::Build(poParent, nFieldIdx, bAscending, op,
     434         404 :                                        eOGRFieldType, psValue);
     435             : }
     436             : 
     437             : /************************************************************************/
     438             : /*                           BuildIsNotNull()                           */
     439             : /************************************************************************/
     440             : 
     441          85 : FileGDBIterator *FileGDBIterator::BuildIsNotNull(FileGDBTable *poParent,
     442             :                                                  int nFieldIdx, int bAscending)
     443             : {
     444          85 :     FileGDBIterator *poIter = Build(poParent, nFieldIdx, bAscending,
     445             :                                     FGSO_ISNOTNULL, OFTMaxType, nullptr);
     446          85 :     if (poIter != nullptr)
     447             :     {
     448             :         /* Optimization */
     449          85 :         if (poIter->GetRowCount() == poParent->GetTotalRecordCount())
     450             :         {
     451          72 :             CPLAssert(poParent->GetValidRecordCount() ==
     452             :                       poParent->GetTotalRecordCount());
     453          72 :             poIter = new FileGDBTrivialIterator(poIter);
     454             :         }
     455             :     }
     456          85 :     return poIter;
     457             : }
     458             : 
     459             : /************************************************************************/
     460             : /*                              BuildNot()                              */
     461             : /************************************************************************/
     462             : 
     463          15 : FileGDBIterator *FileGDBIterator::BuildNot(FileGDBIterator *poIterBase)
     464             : {
     465          15 :     return new FileGDBNotIterator(poIterBase);
     466             : }
     467             : 
     468             : /************************************************************************/
     469             : /*                               BuildAnd()                             */
     470             : /************************************************************************/
     471             : 
     472          11 : FileGDBIterator *FileGDBIterator::BuildAnd(FileGDBIterator *poIter1,
     473             :                                            FileGDBIterator *poIter2,
     474             :                                            bool bTakeOwnershipOfIterators)
     475             : {
     476          11 :     return new FileGDBAndIterator(poIter1, poIter2, bTakeOwnershipOfIterators);
     477             : }
     478             : 
     479             : /************************************************************************/
     480             : /*                               BuildOr()                              */
     481             : /************************************************************************/
     482             : 
     483          28 : FileGDBIterator *FileGDBIterator::BuildOr(FileGDBIterator *poIter1,
     484             :                                           FileGDBIterator *poIter2,
     485             :                                           int bIteratorAreExclusive)
     486             : {
     487          28 :     return new FileGDBOrIterator(poIter1, poIter2, bIteratorAreExclusive);
     488             : }
     489             : 
     490             : /************************************************************************/
     491             : /*                           GetRowCount()                              */
     492             : /************************************************************************/
     493             : 
     494          17 : int64_t FileGDBIterator::GetRowCount()
     495             : {
     496          17 :     Reset();
     497          17 :     int64_t nCount = 0;
     498          67 :     while (GetNextRowSortedByFID() >= 0)
     499          50 :         nCount++;
     500          17 :     Reset();
     501          17 :     return nCount;
     502             : }
     503             : 
     504             : /************************************************************************/
     505             : /*                         FileGDBTrivialIterator()                     */
     506             : /************************************************************************/
     507             : 
     508          72 : FileGDBTrivialIterator::FileGDBTrivialIterator(FileGDBIterator *poParentIterIn)
     509          72 :     : poParentIter(poParentIterIn), poTable(poParentIterIn->GetTable())
     510             : {
     511          72 : }
     512             : 
     513             : /************************************************************************/
     514             : /*                        GetNextRowSortedByFID()                       */
     515             : /************************************************************************/
     516             : 
     517          38 : int64_t FileGDBTrivialIterator::GetNextRowSortedByFID()
     518             : {
     519          38 :     if (iRow < poTable->GetTotalRecordCount())
     520          30 :         return iRow++;
     521             :     else
     522           8 :         return -1;
     523             : }
     524             : 
     525             : /************************************************************************/
     526             : /*                           FileGDBNotIterator()                       */
     527             : /************************************************************************/
     528             : 
     529          15 : FileGDBNotIterator::FileGDBNotIterator(FileGDBIterator *poIterBaseIn)
     530          15 :     : poIterBase(poIterBaseIn), poTable(poIterBaseIn->GetTable())
     531             : {
     532          15 :     bNoHoles =
     533          15 :         (poTable->GetValidRecordCount() == poTable->GetTotalRecordCount());
     534          15 : }
     535             : 
     536             : /************************************************************************/
     537             : /*                          ~FileGDBNotIterator()                       */
     538             : /************************************************************************/
     539             : 
     540          30 : FileGDBNotIterator::~FileGDBNotIterator()
     541             : {
     542          15 :     delete poIterBase;
     543          30 : }
     544             : 
     545             : /************************************************************************/
     546             : /*                             Reset()                                  */
     547             : /************************************************************************/
     548             : 
     549          13 : void FileGDBNotIterator::Reset()
     550             : {
     551          13 :     poIterBase->Reset();
     552          13 :     iRow = 0;
     553          13 :     iNextRowBase = -1;
     554          13 : }
     555             : 
     556             : /************************************************************************/
     557             : /*                        GetNextRowSortedByFID()                       */
     558             : /************************************************************************/
     559             : 
     560         832 : int64_t FileGDBNotIterator::GetNextRowSortedByFID()
     561             : {
     562         832 :     if (iNextRowBase < 0)
     563             :     {
     564          20 :         iNextRowBase = poIterBase->GetNextRowSortedByFID();
     565          20 :         if (iNextRowBase < 0)
     566           2 :             iNextRowBase = poTable->GetTotalRecordCount();
     567             :     }
     568             : 
     569             :     while (true)
     570             :     {
     571        1137 :         if (iRow < iNextRowBase)
     572             :         {
     573         811 :             if (bNoHoles)
     574         811 :                 return iRow++;
     575           0 :             else if (poTable->GetOffsetInTableForRow(iRow))
     576           0 :                 return iRow++;
     577           0 :             else if (!poTable->HasGotError())
     578           0 :                 iRow++;
     579             :             else
     580           0 :                 return -1;
     581             :         }
     582         326 :         else if (iRow == poTable->GetTotalRecordCount())
     583          21 :             return -1;
     584             :         else
     585             :         {
     586         305 :             iRow = iNextRowBase + 1;
     587         305 :             iNextRowBase = poIterBase->GetNextRowSortedByFID();
     588         305 :             if (iNextRowBase < 0)
     589          18 :                 iNextRowBase = poTable->GetTotalRecordCount();
     590             :         }
     591             :     }
     592             : }
     593             : 
     594             : /************************************************************************/
     595             : /*                           GetRowCount()                              */
     596             : /************************************************************************/
     597             : 
     598          10 : int64_t FileGDBNotIterator::GetRowCount()
     599             : {
     600          10 :     return poTable->GetValidRecordCount() - poIterBase->GetRowCount();
     601             : }
     602             : 
     603             : /************************************************************************/
     604             : /*                          FileGDBAndIterator()                        */
     605             : /************************************************************************/
     606             : 
     607          11 : FileGDBAndIterator::FileGDBAndIterator(FileGDBIterator *poIter1In,
     608             :                                        FileGDBIterator *poIter2In,
     609          11 :                                        bool bTakeOwnershipOfIterators)
     610             :     : poIter1(poIter1In), poIter2(poIter2In), iNextRow1(-1), iNextRow2(-1),
     611          11 :       m_bTakeOwnershipOfIterators(bTakeOwnershipOfIterators)
     612             : {
     613          11 :     CPLAssert(poIter1->GetTable() == poIter2->GetTable());
     614          11 : }
     615             : 
     616             : /************************************************************************/
     617             : /*                          ~FileGDBAndIterator()                       */
     618             : /************************************************************************/
     619             : 
     620          22 : FileGDBAndIterator::~FileGDBAndIterator()
     621             : {
     622          11 :     if (m_bTakeOwnershipOfIterators)
     623             :     {
     624           9 :         delete poIter1;
     625           9 :         delete poIter2;
     626             :     }
     627          22 : }
     628             : 
     629             : /************************************************************************/
     630             : /*                             Reset()                                  */
     631             : /************************************************************************/
     632             : 
     633          25 : void FileGDBAndIterator::Reset()
     634             : {
     635          25 :     poIter1->Reset();
     636          25 :     poIter2->Reset();
     637          25 :     iNextRow1 = -1;
     638          25 :     iNextRow2 = -1;
     639          25 : }
     640             : 
     641             : /************************************************************************/
     642             : /*                        GetNextRowSortedByFID()                       */
     643             : /************************************************************************/
     644             : 
     645          50 : int64_t FileGDBAndIterator::GetNextRowSortedByFID()
     646             : {
     647          50 :     if (iNextRow1 == iNextRow2)
     648             :     {
     649          50 :         iNextRow1 = poIter1->GetNextRowSortedByFID();
     650          50 :         iNextRow2 = poIter2->GetNextRowSortedByFID();
     651          50 :         if (iNextRow1 < 0 || iNextRow2 < 0)
     652             :         {
     653          16 :             return -1;
     654             :         }
     655             :     }
     656             : 
     657             :     while (true)
     658             :     {
     659         716 :         if (iNextRow1 < iNextRow2)
     660             :         {
     661         348 :             iNextRow1 = poIter1->GetNextRowSortedByFID();
     662         348 :             if (iNextRow1 < 0)
     663           6 :                 return -1;
     664             :         }
     665         368 :         else if (iNextRow2 < iNextRow1)
     666             :         {
     667         340 :             iNextRow2 = poIter2->GetNextRowSortedByFID();
     668         340 :             if (iNextRow2 < 0)
     669           0 :                 return -1;
     670             :         }
     671             :         else
     672          28 :             return iNextRow1;
     673             :     }
     674             : }
     675             : 
     676             : /************************************************************************/
     677             : /*                          FileGDBOrIterator()                         */
     678             : /************************************************************************/
     679             : 
     680          28 : FileGDBOrIterator::FileGDBOrIterator(FileGDBIterator *poIter1In,
     681             :                                      FileGDBIterator *poIter2In,
     682          28 :                                      int bIteratorAreExclusiveIn)
     683             :     : poIter1(poIter1In), poIter2(poIter2In),
     684          28 :       bIteratorAreExclusive(bIteratorAreExclusiveIn)
     685             : {
     686          28 :     CPLAssert(poIter1->GetTable() == poIter2->GetTable());
     687          28 : }
     688             : 
     689             : /************************************************************************/
     690             : /*                          ~FileGDBOrIterator()                        */
     691             : /************************************************************************/
     692             : 
     693          56 : FileGDBOrIterator::~FileGDBOrIterator()
     694             : {
     695          28 :     delete poIter1;
     696          28 :     delete poIter2;
     697          56 : }
     698             : 
     699             : /************************************************************************/
     700             : /*                             Reset()                                  */
     701             : /************************************************************************/
     702             : 
     703          26 : void FileGDBOrIterator::Reset()
     704             : {
     705          26 :     poIter1->Reset();
     706          26 :     poIter2->Reset();
     707          26 :     iNextRow1 = -1;
     708          26 :     iNextRow2 = -1;
     709          26 :     bHasJustReset = true;
     710          26 : }
     711             : 
     712             : /************************************************************************/
     713             : /*                        GetNextRowSortedByFID()                       */
     714             : /************************************************************************/
     715             : 
     716         163 : int64_t FileGDBOrIterator::GetNextRowSortedByFID()
     717             : {
     718         163 :     if (bHasJustReset)
     719             :     {
     720          38 :         bHasJustReset = false;
     721          38 :         iNextRow1 = poIter1->GetNextRowSortedByFID();
     722          38 :         iNextRow2 = poIter2->GetNextRowSortedByFID();
     723             :     }
     724             : 
     725         163 :     if (iNextRow1 < 0)
     726             :     {
     727          69 :         auto iVal = iNextRow2;
     728          69 :         iNextRow2 = poIter2->GetNextRowSortedByFID();
     729          69 :         return iVal;
     730             :     }
     731          94 :     if (iNextRow2 < 0 || iNextRow1 < iNextRow2)
     732             :     {
     733          45 :         auto iVal = iNextRow1;
     734          45 :         iNextRow1 = poIter1->GetNextRowSortedByFID();
     735          45 :         return iVal;
     736             :     }
     737          49 :     if (iNextRow2 < iNextRow1)
     738             :     {
     739          24 :         auto iVal = iNextRow2;
     740          24 :         iNextRow2 = poIter2->GetNextRowSortedByFID();
     741          24 :         return iVal;
     742             :     }
     743             : 
     744          25 :     if (bIteratorAreExclusive)
     745           0 :         PrintError();
     746             : 
     747          25 :     auto iVal = iNextRow1;
     748          25 :     iNextRow1 = poIter1->GetNextRowSortedByFID();
     749          25 :     iNextRow2 = poIter2->GetNextRowSortedByFID();
     750          25 :     return iVal;
     751             : }
     752             : 
     753             : /************************************************************************/
     754             : /*                           GetRowCount()                              */
     755             : /************************************************************************/
     756             : 
     757          22 : int64_t FileGDBOrIterator::GetRowCount()
     758             : {
     759          22 :     if (bIteratorAreExclusive)
     760          14 :         return poIter1->GetRowCount() + poIter2->GetRowCount();
     761             :     else
     762           8 :         return FileGDBIterator::GetRowCount();
     763             : }
     764             : 
     765             : /************************************************************************/
     766             : /*                     FileGDBIndexIteratorBase()                       */
     767             : /************************************************************************/
     768             : 
     769         765 : FileGDBIndexIteratorBase::FileGDBIndexIteratorBase(FileGDBTable *poParentIn,
     770           0 :                                                    int bAscendingIn)
     771         765 :     : poParent(poParentIn), bAscending(CPL_TO_BOOL(bAscendingIn))
     772             : {
     773             : #ifdef DEBUG
     774         765 :     memset(&iLoadedPage, 0, sizeof(iLoadedPage));
     775             : #endif
     776         765 :     memset(&iFirstPageIdx, 0xFF, sizeof(iFirstPageIdx));
     777         765 :     memset(&iLastPageIdx, 0xFF, sizeof(iFirstPageIdx));
     778         765 :     memset(&iCurPageIdx, 0xFF, sizeof(iCurPageIdx));
     779         765 :     memset(&nSubPagesCount, 0, sizeof(nSubPagesCount));
     780         765 :     memset(&nLastPageAccessed, 0, sizeof(nLastPageAccessed));
     781         765 :     memset(&abyPage, 0, sizeof(abyPage));
     782         765 :     memset(&abyPageFeature, 0, sizeof(abyPageFeature));
     783         765 : }
     784             : 
     785             : /************************************************************************/
     786             : /*                       ~FileGDBIndexIteratorBase()                    */
     787             : /************************************************************************/
     788             : 
     789         765 : FileGDBIndexIteratorBase::~FileGDBIndexIteratorBase()
     790             : {
     791         765 :     if (fpCurIdx)
     792         764 :         VSIFCloseL(fpCurIdx);
     793         765 :     fpCurIdx = nullptr;
     794         765 : }
     795             : 
     796             : /************************************************************************/
     797             : /*                           ReadTrailer()                              */
     798             : /************************************************************************/
     799             : 
     800         765 : bool FileGDBIndexIteratorBase::ReadTrailer(const std::string &osFilename)
     801             : {
     802         765 :     const bool errorRetValue = false;
     803             : 
     804         765 :     fpCurIdx = VSIFOpenL(osFilename.c_str(), "rb");
     805         765 :     returnErrorIf(fpCurIdx == nullptr);
     806             : 
     807         764 :     VSIFSeekL(fpCurIdx, 0, SEEK_END);
     808         764 :     vsi_l_offset nFileSize = VSIFTellL(fpCurIdx);
     809         764 :     constexpr int V1_TRAILER_SIZE = 22;
     810         764 :     constexpr int V2_TRAILER_SIZE = 30;
     811         764 :     returnErrorIf(nFileSize < V1_TRAILER_SIZE);
     812             : 
     813             :     GByte abyTrailer[V2_TRAILER_SIZE];
     814         764 :     VSIFSeekL(fpCurIdx, nFileSize - sizeof(uint32_t), SEEK_SET);
     815         764 :     returnErrorIf(VSIFReadL(abyTrailer, sizeof(uint32_t), 1, fpCurIdx) != 1);
     816         764 :     m_nVersion = GetUInt32(abyTrailer, 0);
     817         764 :     returnErrorIf(m_nVersion != 1 && m_nVersion != 2);
     818             : 
     819         764 :     if (m_nVersion == 1)
     820             :     {
     821         757 :         m_nPageSize = FGDB_PAGE_SIZE_V1;
     822         757 :         VSIFSeekL(fpCurIdx, nFileSize - V1_TRAILER_SIZE, SEEK_SET);
     823         757 :         returnErrorIf(VSIFReadL(abyTrailer, V1_TRAILER_SIZE, 1, fpCurIdx) != 1);
     824             : 
     825         757 :         m_nPageCount =
     826         757 :             static_cast<GUInt32>((nFileSize - V1_TRAILER_SIZE) / m_nPageSize);
     827             : 
     828         757 :         m_nValueSize = abyTrailer[0];
     829         757 :         m_nObjectIDSize = static_cast<uint32_t>(sizeof(uint32_t));
     830         757 :         m_nNonLeafPageHeaderSize = 8;
     831         757 :         m_nLeafPageHeaderSize = 12;
     832             : 
     833         757 :         nMaxPerPages = (m_nPageSize - m_nLeafPageHeaderSize) /
     834         757 :                        (m_nObjectIDSize + m_nValueSize);
     835         757 :         m_nOffsetFirstValInPage =
     836         757 :             m_nLeafPageHeaderSize + nMaxPerPages * m_nObjectIDSize;
     837             : 
     838         757 :         GUInt32 nMagic1 = GetUInt32(abyTrailer + 2, 0);
     839         757 :         returnErrorIf(nMagic1 != 1);
     840             : 
     841         756 :         nIndexDepth = GetUInt32(abyTrailer + 6, 0);
     842             :         /* CPLDebug("OpenFileGDB", "nIndexDepth = %u", nIndexDepth); */
     843         756 :         returnErrorIf(!(nIndexDepth >= 1 && nIndexDepth <= MAX_DEPTH + 1));
     844             : 
     845         755 :         m_nValueCountInIdx = GetUInt32(abyTrailer + 10, 0);
     846             :         /* CPLDebug("OpenFileGDB", "m_nValueCountInIdx = %u", m_nValueCountInIdx); */
     847             :         /* negative like in sample_clcV15_esri_v10.gdb/a00000005.FDO_UUID.atx */
     848         755 :         if ((m_nValueCountInIdx >> (8 * sizeof(m_nValueCountInIdx) - 1)) != 0)
     849             :         {
     850           0 :             CPLDebugOnly("OpenFileGDB", "m_nValueCountInIdx=%u",
     851             :                          static_cast<uint32_t>(m_nValueCountInIdx));
     852           0 :             return false;
     853             :         }
     854             : 
     855             :         /* QGIS_TEST_101.gdb/a00000006.FDO_UUID.atx */
     856             :         /* or .spx file from test dataset https://github.com/OSGeo/gdal/issues/5888
     857             :          */
     858         755 :         if (m_nValueCountInIdx == 0 && nIndexDepth == 1)
     859             :         {
     860          39 :             VSIFSeekL(fpCurIdx, 4, SEEK_SET);
     861             :             GByte abyBuffer[4];
     862          39 :             returnErrorIf(VSIFReadL(abyBuffer, 4, 1, fpCurIdx) != 1);
     863          39 :             m_nValueCountInIdx = GetUInt32(abyBuffer, 0);
     864             :         }
     865             :         /* PreNIS.gdb/a00000006.FDO_UUID.atx has depth 2 and the value of */
     866             :         /* m_nValueCountInIdx is 11 which is not the number of non-null values */
     867         716 :         else if (m_nValueCountInIdx < nMaxPerPages && nIndexDepth > 1)
     868             :         {
     869         196 :             if (m_nValueCountInIdx > 0 && poParent->IsFileGDBV9() &&
     870           2 :                 strstr(osFilename.c_str(), "blk_key_index.atx"))
     871             :             {
     872             :                 // m_nValueCountInIdx not reliable in FileGDB v9 .blk_key_index.atx
     873             :                 // but index seems to be OK
     874           2 :                 return true;
     875             :             }
     876             : 
     877         192 :             CPLDebugOnly(
     878             :                 "OpenFileGDB",
     879             :                 "m_nValueCountInIdx=%u < nMaxPerPages=%u, nIndexDepth=%u",
     880             :                 static_cast<uint32_t>(m_nValueCountInIdx), nMaxPerPages,
     881             :                 nIndexDepth);
     882         192 :             return false;
     883             :         }
     884             :     }
     885             :     else
     886             :     {
     887           7 :         m_nPageSize = FGDB_PAGE_SIZE_V2;
     888           7 :         VSIFSeekL(fpCurIdx, nFileSize - V2_TRAILER_SIZE, SEEK_SET);
     889           7 :         returnErrorIf(VSIFReadL(abyTrailer, V2_TRAILER_SIZE, 1, fpCurIdx) != 1);
     890             : 
     891           7 :         m_nPageCount =
     892           7 :             static_cast<GUInt32>((nFileSize - V2_TRAILER_SIZE) / m_nPageSize);
     893             : 
     894           7 :         m_nValueSize = abyTrailer[0];
     895           7 :         m_nObjectIDSize = static_cast<uint32_t>(sizeof(uint64_t));
     896           7 :         m_nNonLeafPageHeaderSize = 12;
     897           7 :         m_nLeafPageHeaderSize = 20;
     898             : 
     899           7 :         nMaxPerPages = (m_nPageSize - m_nLeafPageHeaderSize) /
     900           7 :                        (m_nObjectIDSize + m_nValueSize);
     901           7 :         m_nOffsetFirstValInPage =
     902           7 :             m_nLeafPageHeaderSize + nMaxPerPages * m_nObjectIDSize;
     903             : 
     904           7 :         GUInt32 nMagic1 = GetUInt32(abyTrailer + 2, 0);
     905           7 :         returnErrorIf(nMagic1 != 1);
     906             : 
     907           7 :         nIndexDepth = GetUInt32(abyTrailer + 6, 0);
     908             :         /* CPLDebug("OpenFileGDB", "nIndexDepth = %u", nIndexDepth); */
     909           7 :         returnErrorIf(!(nIndexDepth >= 1 && nIndexDepth <= MAX_DEPTH + 1));
     910             : 
     911           7 :         m_nValueCountInIdx = GetUInt64(abyTrailer + 10, 0);
     912             :     }
     913             : 
     914         568 :     return true;
     915             : }
     916             : 
     917             : /************************************************************************/
     918             : /*                         FileGDBIndexIterator()                       */
     919             : /************************************************************************/
     920             : 
     921         404 : FileGDBIndexIterator::FileGDBIndexIterator(FileGDBTable *poParentIn,
     922         404 :                                            int bAscendingIn)
     923         404 :     : FileGDBIndexIteratorBase(poParentIn, bAscendingIn), nStrLen(0)
     924             : {
     925         404 :     memset(&sValue, 0, sizeof(sValue));
     926         404 :     memset(&asUTF16Str, 0, sizeof(asUTF16Str));
     927         404 :     memset(&szUUID, 0, sizeof(szUUID));
     928         404 :     memset(&sMin, 0, sizeof(sMin));
     929         404 :     memset(&sMax, 0, sizeof(sMax));
     930         404 :     memset(&szMin, 0, sizeof(szMin));
     931         404 :     memset(&szMax, 0, sizeof(szMax));
     932         404 : }
     933             : 
     934             : /************************************************************************/
     935             : /*                         ~FileGDBIndexIterator()                      */
     936             : /************************************************************************/
     937             : 
     938         808 : FileGDBIndexIterator::~FileGDBIndexIterator()
     939             : {
     940         404 :     VSIFree(panSortedRows);
     941         808 : }
     942             : 
     943             : /************************************************************************/
     944             : /*                             Build()                                  */
     945             : /************************************************************************/
     946             : 
     947         404 : FileGDBIterator *FileGDBIndexIterator::Build(FileGDBTable *poParentIn,
     948             :                                              int nFieldIdx, int bAscendingIn,
     949             :                                              FileGDBSQLOp op,
     950             :                                              OGRFieldType eOGRFieldType,
     951             :                                              const OGRField *psValue)
     952             : {
     953             :     FileGDBIndexIterator *poIndexIterator =
     954         404 :         new FileGDBIndexIterator(poParentIn, bAscendingIn);
     955         404 :     if (poIndexIterator->SetConstraint(nFieldIdx, op, eOGRFieldType, psValue))
     956             :     {
     957         399 :         return poIndexIterator;
     958             :     }
     959           5 :     delete poIndexIterator;
     960           5 :     return nullptr;
     961             : }
     962             : 
     963             : /************************************************************************/
     964             : /*                           FileGDBSQLOpToStr()                        */
     965             : /************************************************************************/
     966             : 
     967         250 : static const char *FileGDBSQLOpToStr(FileGDBSQLOp op)
     968             : {
     969         250 :     switch (op)
     970             :     {
     971          56 :         case FGSO_ISNOTNULL:
     972          56 :             return "IS NOT NULL";
     973          21 :         case FGSO_LT:
     974          21 :             return "<";
     975          18 :         case FGSO_LE:
     976          18 :             return "<=";
     977         110 :         case FGSO_EQ:
     978         110 :             return "=";
     979          23 :         case FGSO_GE:
     980          23 :             return ">=";
     981          19 :         case FGSO_GT:
     982          19 :             return ">";
     983           3 :         case FGSO_ILIKE:
     984           3 :             return "ILIKE";
     985             :     }
     986           0 :     return "unknown_op";
     987             : }
     988             : 
     989             : /************************************************************************/
     990             : /*                           FileGDBValueToStr()                        */
     991             : /************************************************************************/
     992             : 
     993         250 : static const char *FileGDBValueToStr(OGRFieldType eOGRFieldType,
     994             :                                      const OGRField *psValue)
     995             : {
     996         250 :     if (psValue == nullptr)
     997          56 :         return "";
     998             : 
     999         194 :     switch (eOGRFieldType)
    1000             :     {
    1001         102 :         case OFTInteger:
    1002         102 :             return CPLSPrintf("%d", psValue->Integer);
    1003          38 :         case OFTReal:
    1004          38 :             return CPLSPrintf("%.17g", psValue->Real);
    1005          42 :         case OFTString:
    1006          42 :             return psValue->String;
    1007           8 :         case OFTDateTime:
    1008          16 :             return CPLSPrintf(
    1009           8 :                 "%04d/%02d/%02d %02d:%02d:%02d", psValue->Date.Year,
    1010           8 :                 psValue->Date.Month, psValue->Date.Day, psValue->Date.Hour,
    1011           8 :                 psValue->Date.Minute, static_cast<int>(psValue->Date.Second));
    1012           0 :         case OFTDate:
    1013           0 :             return CPLSPrintf("%04d/%02d/%02d", psValue->Date.Year,
    1014           0 :                               psValue->Date.Month, psValue->Date.Day);
    1015           0 :         case OFTTime:
    1016           0 :             return CPLSPrintf("%02d:%02d:%02d", psValue->Date.Hour,
    1017           0 :                               psValue->Date.Minute,
    1018           0 :                               static_cast<int>(psValue->Date.Second));
    1019           4 :         default:
    1020           4 :             break;
    1021             :     }
    1022           4 :     return "";
    1023             : }
    1024             : 
    1025             : /************************************************************************/
    1026             : /*                          GetMaxWidthInBytes()                        */
    1027             : /************************************************************************/
    1028             : 
    1029         160 : int FileGDBIndex::GetMaxWidthInBytes(const FileGDBTable *poTable) const
    1030             : {
    1031             :     const std::string osAtxName = CPLResetExtensionSafe(
    1032         320 :         poTable->GetFilename().c_str(), (GetIndexName() + ".atx").c_str());
    1033         160 :     VSILFILE *fpCurIdx = VSIFOpenL(osAtxName.c_str(), "rb");
    1034         160 :     if (fpCurIdx == nullptr)
    1035           1 :         return 0;
    1036             : 
    1037         159 :     VSIFSeekL(fpCurIdx, 0, SEEK_END);
    1038         159 :     vsi_l_offset nFileSize = VSIFTellL(fpCurIdx);
    1039             : 
    1040         159 :     constexpr int V1_TRAILER_SIZE = 22;
    1041         159 :     constexpr int V2_TRAILER_SIZE = 30;
    1042             : 
    1043         159 :     if (nFileSize < FGDB_PAGE_SIZE_V1 + V1_TRAILER_SIZE)
    1044             :     {
    1045           0 :         VSIFCloseL(fpCurIdx);
    1046           0 :         return 0;
    1047             :     }
    1048             : 
    1049             :     GByte abyTrailer[V2_TRAILER_SIZE];
    1050         159 :     VSIFSeekL(fpCurIdx, nFileSize - sizeof(uint32_t), SEEK_SET);
    1051         159 :     if (VSIFReadL(abyTrailer, sizeof(uint32_t), 1, fpCurIdx) != 1)
    1052             :     {
    1053           0 :         VSIFCloseL(fpCurIdx);
    1054           0 :         return 0;
    1055             :     }
    1056         159 :     const auto nVersion = GetUInt32(abyTrailer, 0);
    1057         159 :     if (nVersion != 1 && nVersion != 2)
    1058             :     {
    1059           0 :         VSIFCloseL(fpCurIdx);
    1060           0 :         return 0;
    1061             :     }
    1062             : 
    1063         159 :     const int nTrailerSize = nVersion == 1 ? V1_TRAILER_SIZE : V2_TRAILER_SIZE;
    1064             : 
    1065         159 :     if (nVersion == 2 && nFileSize < FGDB_PAGE_SIZE_V2 + V2_TRAILER_SIZE)
    1066             :     {
    1067           0 :         VSIFCloseL(fpCurIdx);
    1068           0 :         return 0;
    1069             :     }
    1070             : 
    1071         159 :     VSIFSeekL(fpCurIdx, nFileSize - nTrailerSize, SEEK_SET);
    1072         159 :     if (VSIFReadL(abyTrailer, nTrailerSize, 1, fpCurIdx) != 1)
    1073             :     {
    1074           0 :         VSIFCloseL(fpCurIdx);
    1075           0 :         return 0;
    1076             :     }
    1077             : 
    1078         159 :     const int nRet = abyTrailer[0];
    1079         159 :     VSIFCloseL(fpCurIdx);
    1080         159 :     return nRet;
    1081             : }
    1082             : 
    1083             : /************************************************************************/
    1084             : /*                           SetConstraint()                            */
    1085             : /************************************************************************/
    1086             : 
    1087         404 : int FileGDBIndexIterator::SetConstraint(int nFieldIdx, FileGDBSQLOp op,
    1088             :                                         OGRFieldType eOGRFieldType,
    1089             :                                         const OGRField *psValue)
    1090             : {
    1091         404 :     const int errorRetValue = FALSE;
    1092         404 :     CPLAssert(fpCurIdx == nullptr);
    1093             : 
    1094         404 :     returnErrorIf(nFieldIdx < 0 || nFieldIdx >= poParent->GetFieldCount());
    1095         404 :     FileGDBField *poField = poParent->GetField(nFieldIdx);
    1096         404 :     returnErrorIf(!(poField->HasIndex()));
    1097             : 
    1098         404 :     eFieldType = poField->GetType();
    1099         404 :     eOp = op;
    1100             : 
    1101         404 :     returnErrorIf(eFieldType != FGFT_INT16 && eFieldType != FGFT_INT32 &&
    1102             :                   eFieldType != FGFT_FLOAT32 && eFieldType != FGFT_FLOAT64 &&
    1103             :                   eFieldType != FGFT_STRING && eFieldType != FGFT_DATETIME &&
    1104             :                   eFieldType != FGFT_GUID && eFieldType != FGFT_GLOBALID &&
    1105             :                   eFieldType != FGFT_INT64 && eFieldType != FGFT_DATE &&
    1106             :                   eFieldType != FGFT_TIME &&
    1107             :                   eFieldType != FGFT_DATETIME_WITH_OFFSET);
    1108             : 
    1109         404 :     const auto poIndex = poField->GetIndex();
    1110             : 
    1111             :     // Only supports ILIKE on a field string if the index expression starts
    1112             :     // with LOWER() and the string to compare with is only ASCII without
    1113             :     // wildcards
    1114         571 :     if (eOGRFieldType == OFTString &&
    1115         167 :         STARTS_WITH_CI(poIndex->GetExpression().c_str(), "LOWER("))
    1116             :     {
    1117           3 :         if (eOp == FGSO_ILIKE)
    1118             :         {
    1119           3 :             if (!CPLIsASCII(psValue->String, strlen(psValue->String)) ||
    1120           3 :                 strchr(psValue->String, '%') || strchr(psValue->String, '_'))
    1121             :             {
    1122           0 :                 return FALSE;
    1123             :             }
    1124             :         }
    1125           0 :         else if (eOp != FGSO_ISNOTNULL)
    1126             :         {
    1127           0 :             return FALSE;
    1128             :         }
    1129             :     }
    1130         401 :     else if (eOp == FGSO_ILIKE)
    1131             :     {
    1132           0 :         return FALSE;
    1133             :     }
    1134             : 
    1135         404 :     if (CPLHasPathTraversal(poIndex->GetIndexName().c_str()))
    1136             :     {
    1137           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Path traversal detected in %s",
    1138           0 :                  poIndex->GetIndexName().c_str());
    1139           0 :         return FALSE;
    1140             :     }
    1141             : 
    1142             :     const std::string osAtxName =
    1143        1616 :         CPLFormFilenameSafe(
    1144         808 :             CPLGetPathSafe(poParent->GetFilename().c_str()).c_str(),
    1145         404 :             CPLGetBasenameSafe(poParent->GetFilename().c_str()).c_str(),
    1146         404 :             poIndex->GetIndexName().c_str())
    1147         808 :             .append(".atx");
    1148             : 
    1149         404 :     if (!ReadTrailer(osAtxName.c_str()))
    1150           3 :         return FALSE;
    1151         401 :     returnErrorIf(m_nValueCountInIdx >
    1152             :                   static_cast<GUInt64>(poParent->GetValidRecordCount()));
    1153             : 
    1154         400 :     switch (eFieldType)
    1155             :     {
    1156          10 :         case FGFT_INT16:
    1157          10 :             returnErrorIf(m_nValueSize != sizeof(GUInt16));
    1158          10 :             if (eOp != FGSO_ISNOTNULL)
    1159             :             {
    1160           8 :                 returnErrorIf(eOGRFieldType != OFTInteger);
    1161           8 :                 sValue.Integer = psValue->Integer;
    1162             :             }
    1163          10 :             break;
    1164         118 :         case FGFT_INT32:
    1165         118 :             returnErrorIf(m_nValueSize != sizeof(GUInt32));
    1166         118 :             if (eOp != FGSO_ISNOTNULL)
    1167             :             {
    1168          94 :                 returnErrorIf(eOGRFieldType != OFTInteger);
    1169          94 :                 sValue.Integer = psValue->Integer;
    1170             :             }
    1171         118 :             break;
    1172          20 :         case FGFT_FLOAT32:
    1173          20 :             returnErrorIf(m_nValueSize != sizeof(float));
    1174          20 :             if (eOp != FGSO_ISNOTNULL)
    1175             :             {
    1176          18 :                 returnErrorIf(eOGRFieldType != OFTReal);
    1177          18 :                 sValue.Real = psValue->Real;
    1178             :             }
    1179          20 :             break;
    1180          32 :         case FGFT_FLOAT64:
    1181          32 :             returnErrorIf(m_nValueSize != sizeof(double));
    1182          32 :             if (eOp != FGSO_ISNOTNULL)
    1183             :             {
    1184          20 :                 returnErrorIf(eOGRFieldType != OFTReal);
    1185          20 :                 sValue.Real = psValue->Real;
    1186             :             }
    1187          32 :             break;
    1188         186 :         case FGFT_STRING:
    1189             :         {
    1190         186 :             returnErrorIf((m_nValueSize % 2) != 0);
    1191         186 :             returnErrorIf(m_nValueSize == 0);
    1192         186 :             returnErrorIf(m_nValueSize > 2 * MAX_CAR_COUNT_INDEXED_STR);
    1193         186 :             nStrLen = m_nValueSize / 2;
    1194         186 :             if (eOp != FGSO_ISNOTNULL)
    1195             :             {
    1196         156 :                 returnErrorIf(eOGRFieldType != OFTString);
    1197         156 :                 wchar_t *pWide = CPLRecodeToWChar(psValue->String, CPL_ENC_UTF8,
    1198             :                                                   CPL_ENC_UCS2);
    1199         156 :                 returnErrorIf(pWide == nullptr);
    1200         156 :                 int nCount = 0;
    1201        2497 :                 while (pWide[nCount] != 0)
    1202             :                 {
    1203        2341 :                     returnErrorAndCleanupIf(nCount == nStrLen, CPLFree(pWide));
    1204        2341 :                     asUTF16Str[nCount] = pWide[nCount];
    1205        2341 :                     nCount++;
    1206             :                 }
    1207        1970 :                 while (nCount < nStrLen)
    1208             :                 {
    1209        1814 :                     asUTF16Str[nCount] = 32; /* space character */
    1210        1814 :                     nCount++;
    1211             :                 }
    1212         156 :                 CPLFree(pWide);
    1213             :             }
    1214         186 :             break;
    1215             :         }
    1216             : 
    1217          16 :         case FGFT_DATETIME:
    1218             :         case FGFT_DATE:
    1219             :         case FGFT_DATETIME_WITH_OFFSET:
    1220             :         {
    1221          16 :             returnErrorIf(m_nValueSize != sizeof(double));
    1222          16 :             if (eOp != FGSO_ISNOTNULL)
    1223             :             {
    1224           8 :                 returnErrorIf(
    1225             :                     eOGRFieldType != OFTReal && eOGRFieldType != OFTDateTime &&
    1226             :                     eOGRFieldType != OFTDate && eOGRFieldType != OFTTime);
    1227           8 :                 if (eOGRFieldType == OFTReal)
    1228           0 :                     sValue.Real = psValue->Real;
    1229             :                 else
    1230           8 :                     sValue.Real = FileGDBOGRDateToDoubleDate(
    1231             :                         psValue, true,
    1232           8 :                         /* bHighPrecision= */ eFieldType ==
    1233          16 :                                 FGFT_DATETIME_WITH_OFFSET ||
    1234           8 :                             poField->IsHighPrecision());
    1235             :             }
    1236          16 :             break;
    1237             :         }
    1238             : 
    1239           8 :         case FGFT_GUID:
    1240             :         case FGFT_GLOBALID:
    1241             :         {
    1242           8 :             returnErrorIf(m_nValueSize != UUID_LEN_AS_STRING);
    1243           8 :             if (eOp != FGSO_ISNOTNULL)
    1244             :             {
    1245           7 :                 returnErrorIf(eOGRFieldType != OFTString);
    1246           7 :                 memset(szUUID, 0, UUID_LEN_AS_STRING + 1);
    1247             :                 // cppcheck-suppress redundantCopy
    1248           7 :                 strncpy(szUUID, psValue->String, UUID_LEN_AS_STRING);
    1249           9 :                 bEvaluateToFALSE = eOp == FGSO_EQ &&
    1250           2 :                                    strlen(psValue->String) !=
    1251             :                                        static_cast<size_t>(UUID_LEN_AS_STRING);
    1252             :             }
    1253           8 :             break;
    1254             :         }
    1255             : 
    1256           8 :         case FGFT_INT64:
    1257           8 :             returnErrorIf(m_nValueSize != sizeof(int64_t));
    1258           8 :             if (eOp != FGSO_ISNOTNULL)
    1259             :             {
    1260           4 :                 returnErrorIf(eOGRFieldType != OFTInteger64);
    1261           4 :                 sValue.Integer64 = psValue->Integer64;
    1262             :             }
    1263           8 :             break;
    1264             : 
    1265           2 :         case FGFT_TIME:
    1266             :         {
    1267           2 :             returnErrorIf(m_nValueSize != sizeof(double));
    1268           2 :             if (eOp != FGSO_ISNOTNULL)
    1269             :             {
    1270           0 :                 returnErrorIf(eOGRFieldType != OFTReal &&
    1271             :                               eOGRFieldType != OFTTime);
    1272           0 :                 if (eOGRFieldType == OFTReal)
    1273           0 :                     sValue.Real = psValue->Real;
    1274             :                 else
    1275           0 :                     sValue.Real = FileGDBOGRTimeToDoubleTime(psValue);
    1276             :             }
    1277           2 :             break;
    1278             :         }
    1279             : 
    1280           0 :         default:
    1281           0 :             CPLAssert(false);
    1282             :             break;
    1283             :     }
    1284             : 
    1285         400 :     if (m_nValueCountInIdx > 0)
    1286             :     {
    1287         394 :         if (nIndexDepth == 1)
    1288             :         {
    1289         357 :             iFirstPageIdx[0] = iLastPageIdx[0] = 0;
    1290             :         }
    1291             :         else
    1292             :         {
    1293          37 :             returnErrorIf(!FindPages(0, 1));
    1294             :         }
    1295             :     }
    1296             : 
    1297             :     // To avoid 'spamming' on huge raster files
    1298         399 :     if (poField->GetName() != "block_key")
    1299             :     {
    1300         500 :         CPLDebug("OpenFileGDB", "Using index on field %s (%s %s)",
    1301         250 :                  poField->GetName().c_str(), FileGDBSQLOpToStr(eOp),
    1302             :                  FileGDBValueToStr(eOGRFieldType, psValue));
    1303             :     }
    1304             : 
    1305         399 :     Reset();
    1306             : 
    1307         399 :     return TRUE;
    1308             : }
    1309             : 
    1310             : /************************************************************************/
    1311             : /*                          FileGDBUTF16StrCompare()                    */
    1312             : /************************************************************************/
    1313             : 
    1314        1580 : static int FileGDBUTF16StrCompare(const GUInt16 *pasFirst,
    1315             :                                   const GUInt16 *pasSecond, int nStrLen,
    1316             :                                   bool bCaseInsensitive)
    1317             : {
    1318       18115 :     for (int i = 0; i < nStrLen; i++)
    1319             :     {
    1320       17966 :         GUInt16 chA = pasFirst[i];
    1321       17966 :         GUInt16 chB = pasSecond[i];
    1322       17966 :         if (bCaseInsensitive)
    1323             :         {
    1324         109 :             if (chA >= 'a' && chA <= 'z')
    1325          14 :                 chA -= 'a' - 'A';
    1326         109 :             if (chB >= 'a' && chB <= 'z')
    1327          31 :                 chB -= 'a' - 'A';
    1328             :         }
    1329       17966 :         if (chA < chB)
    1330          19 :             return -1;
    1331       17947 :         if (chA > chB)
    1332        1412 :             return 1;
    1333             :     }
    1334         149 :     return 0;
    1335             : }
    1336             : 
    1337             : /************************************************************************/
    1338             : /*                              COMPARE()                               */
    1339             : /************************************************************************/
    1340             : 
    1341             : #define COMPARE(a, b) (((a) < (b)) ? -1 : ((a) == (b)) ? 0 : 1)
    1342             : 
    1343             : /************************************************************************/
    1344             : /*                             FindPages()                              */
    1345             : /************************************************************************/
    1346             : 
    1347          40 : bool FileGDBIndexIterator::FindPages(int iLevel, uint64_t nPage)
    1348             : {
    1349          40 :     const bool errorRetValue = false;
    1350          40 :     VSIFSeekL(fpCurIdx, static_cast<vsi_l_offset>(nPage - 1) * m_nPageSize,
    1351             :               SEEK_SET);
    1352             : #ifdef DEBUG
    1353          40 :     iLoadedPage[iLevel] = nPage;
    1354             : #endif
    1355          40 :     returnErrorIf(VSIFReadL(abyPage[iLevel], m_nPageSize, 1, fpCurIdx) != 1);
    1356             : 
    1357          40 :     nSubPagesCount[iLevel] = GetUInt32(abyPage[iLevel] + m_nObjectIDSize, 0);
    1358          40 :     returnErrorIf(nSubPagesCount[iLevel] == 0 ||
    1359             :                   nSubPagesCount[iLevel] > nMaxPerPages);
    1360          39 :     if (nIndexDepth == 2)
    1361          34 :         returnErrorIf(m_nValueCountInIdx > static_cast<uint64_t>(nMaxPerPages) *
    1362             :                                                (nSubPagesCount[0] + 1));
    1363             : 
    1364          39 :     if (eOp == FGSO_ISNOTNULL)
    1365             :     {
    1366          15 :         iFirstPageIdx[iLevel] = 0;
    1367          15 :         iLastPageIdx[iLevel] = nSubPagesCount[iLevel];
    1368          15 :         return true;
    1369             :     }
    1370             : 
    1371             :     GUInt32 i;
    1372             : #ifdef DEBUG_INDEX_CONSISTENCY
    1373             :     double dfLastMax = 0.0;
    1374             :     int nLastMax = 0;
    1375             :     GUInt16 asLastMax[MAX_CAR_COUNT_INDEXED_STR] = {0};
    1376             :     char szLastMaxUUID[UUID_LEN_AS_STRING + 1] = {0};
    1377             : #endif
    1378          24 :     iFirstPageIdx[iLevel] = iLastPageIdx[iLevel] = -1;
    1379             : 
    1380          46 :     for (i = 0; i < nSubPagesCount[iLevel]; i++)
    1381             :     {
    1382             :         int nComp;
    1383             : 
    1384          26 :         switch (eFieldType)
    1385             :         {
    1386           0 :             case FGFT_INT16:
    1387             :             {
    1388             :                 GInt16 nVal =
    1389           0 :                     GetInt16(abyPage[iLevel] + m_nOffsetFirstValInPage, i);
    1390             : #ifdef DEBUG_INDEX_CONSISTENCY
    1391             :                 returnErrorIf(i > 0 && nVal < nLastMax);
    1392             :                 nLastMax = nVal;
    1393             : #endif
    1394           0 :                 nComp = COMPARE(sValue.Integer, nVal);
    1395           0 :                 break;
    1396             :             }
    1397             : 
    1398           2 :             case FGFT_INT32:
    1399             :             {
    1400             :                 GInt32 nVal =
    1401           2 :                     GetInt32(abyPage[iLevel] + m_nOffsetFirstValInPage, i);
    1402             : #ifdef DEBUG_INDEX_CONSISTENCY
    1403             :                 returnErrorIf(i > 0 && nVal < nLastMax);
    1404             :                 nLastMax = nVal;
    1405             : #endif
    1406           2 :                 nComp = COMPARE(sValue.Integer, nVal);
    1407           2 :                 break;
    1408             :             }
    1409             : 
    1410           0 :             case FGFT_INT64:
    1411             :             {
    1412             :                 int64_t nVal =
    1413           0 :                     GetInt64(abyPage[iLevel] + m_nOffsetFirstValInPage, i);
    1414             : #ifdef DEBUG_INDEX_CONSISTENCY
    1415             :                 returnErrorIf(i > 0 && nVal < nLastMax);
    1416             :                 nLastMax = nVal;
    1417             : #endif
    1418           0 :                 nComp = COMPARE(sValue.Integer64, nVal);
    1419           0 :                 break;
    1420             :             }
    1421             : 
    1422           0 :             case FGFT_FLOAT32:
    1423             :             {
    1424             :                 float fVal =
    1425           0 :                     GetFloat32(abyPage[iLevel] + m_nOffsetFirstValInPage, i);
    1426             : #ifdef DEBUG_INDEX_CONSISTENCY
    1427             :                 returnErrorIf(i > 0 && fVal < dfLastMax);
    1428             :                 dfLastMax = fVal;
    1429             : #endif
    1430           0 :                 nComp = COMPARE(sValue.Real, fVal);
    1431           0 :                 break;
    1432             :             }
    1433             : 
    1434          13 :             case FGFT_FLOAT64:
    1435             :             {
    1436             :                 const double dfVal =
    1437          13 :                     GetFloat64(abyPage[iLevel] + m_nOffsetFirstValInPage, i);
    1438             : #ifdef DEBUG_INDEX_CONSISTENCY
    1439             :                 returnErrorIf(i > 0 && dfVal < dfLastMax);
    1440             :                 dfLastMax = dfVal;
    1441             : #endif
    1442          13 :                 nComp = COMPARE(sValue.Real, dfVal);
    1443          13 :                 break;
    1444             :             }
    1445             : 
    1446           0 :             case FGFT_DATETIME:
    1447             :             case FGFT_DATE:
    1448             :             case FGFT_TIME:
    1449             :             {
    1450             :                 const double dfVal =
    1451           0 :                     GetFloat64(abyPage[iLevel] + m_nOffsetFirstValInPage, i);
    1452             : #ifdef DEBUG_INDEX_CONSISTENCY
    1453             :                 returnErrorIf(i > 0 && dfVal < dfLastMax);
    1454             :                 dfLastMax = dfVal;
    1455             : #endif
    1456           0 :                 if (sValue.Real + 1e-10 < dfVal)
    1457           0 :                     nComp = -1;
    1458           0 :                 else if (sValue.Real - 1e-10 > dfVal)
    1459           0 :                     nComp = 1;
    1460             :                 else
    1461           0 :                     nComp = 0;
    1462           0 :                 break;
    1463             :             }
    1464             : 
    1465          11 :             case FGFT_STRING:
    1466             :             {
    1467             :                 GUInt16 *pasMax;
    1468             :                 GUInt16 asMax[MAX_CAR_COUNT_INDEXED_STR];
    1469          11 :                 pasMax = asMax;
    1470          11 :                 memcpy(asMax,
    1471          11 :                        abyPage[iLevel] + m_nOffsetFirstValInPage +
    1472          11 :                            nStrLen * sizeof(GUInt16) * i,
    1473          11 :                        nStrLen * sizeof(GUInt16));
    1474         717 :                 for (int j = 0; j < nStrLen; j++)
    1475         706 :                     CPL_LSBPTR16(&asMax[j]);
    1476             :                     // Note: we have an inconsistency. OGR SQL equality operator
    1477             :                     // is advertized to be case insensitive, but we have always
    1478             :                     // implemented FGSO_EQ as case sensitive.
    1479             : #ifdef DEBUG_INDEX_CONSISTENCY
    1480             :                 returnErrorIf(i > 0 &&
    1481             :                               FileGDBUTF16StrCompare(pasMax, asLastMax, nStrLen,
    1482             :                                                      eOp == FGSO_ILIKE) < 0);
    1483             :                 memcpy(asLastMax, pasMax, nStrLen * 2);
    1484             : #endif
    1485          11 :                 nComp = FileGDBUTF16StrCompare(asUTF16Str, pasMax, nStrLen,
    1486          11 :                                                eOp == FGSO_ILIKE);
    1487          11 :                 break;
    1488             :             }
    1489             : 
    1490           0 :             case FGFT_GUID:
    1491             :             case FGFT_GLOBALID:
    1492             :             {
    1493           0 :                 const char *psNonzMaxUUID = reinterpret_cast<char *>(
    1494           0 :                     abyPage[iLevel] + m_nOffsetFirstValInPage +
    1495           0 :                     UUID_LEN_AS_STRING * i);
    1496             : #ifdef DEBUG_INDEX_CONSISTENCY
    1497             :                 returnErrorIf(i > 0 && memcmp(psNonzMaxUUID, szLastMaxUUID,
    1498             :                                               UUID_LEN_AS_STRING) < 0);
    1499             :                 memcpy(szLastMaxUUID, psNonzMaxUUID, UUID_LEN_AS_STRING);
    1500             : #endif
    1501           0 :                 nComp = memcmp(szUUID, psNonzMaxUUID, UUID_LEN_AS_STRING);
    1502           0 :                 break;
    1503             :             }
    1504             : 
    1505           0 :             default:
    1506           0 :                 CPLAssert(false);
    1507             :                 nComp = 0;
    1508             :                 break;
    1509             :         }
    1510             : 
    1511          26 :         int bStop = FALSE;
    1512          26 :         switch (eOp)
    1513             :         {
    1514             :             /* dfVal = 1 2 2 3 3 4 */
    1515             :             /* sValue.Real = 3 */
    1516             :             /* nComp = (sValue.Real < dfVal) ? -1 : (sValue.Real == dfVal) ? 0 :
    1517             :              * 1; */
    1518           3 :             case FGSO_LT:
    1519             :             case FGSO_LE:
    1520           3 :                 if (iFirstPageIdx[iLevel] < 0)
    1521             :                 {
    1522           3 :                     iFirstPageIdx[iLevel] = iLastPageIdx[iLevel] =
    1523           3 :                         static_cast<int>(i);
    1524             :                 }
    1525             :                 else
    1526             :                 {
    1527           0 :                     iLastPageIdx[iLevel] = static_cast<int>(i);
    1528           0 :                     if (nComp < 0)
    1529             :                     {
    1530           0 :                         bStop = TRUE;
    1531             :                     }
    1532             :                 }
    1533           3 :                 break;
    1534             : 
    1535          21 :             case FGSO_EQ:
    1536             :             case FGSO_ILIKE:
    1537          21 :                 if (iFirstPageIdx[iLevel] < 0)
    1538             :                 {
    1539          19 :                     if (nComp <= 0)
    1540          11 :                         iFirstPageIdx[iLevel] = iLastPageIdx[iLevel] =
    1541          11 :                             static_cast<int>(i);
    1542             :                 }
    1543             :                 else
    1544             :                 {
    1545           2 :                     if (nComp == 0)
    1546           0 :                         iLastPageIdx[iLevel] = static_cast<int>(i);
    1547             :                     else
    1548           2 :                         bStop = TRUE;
    1549             :                 }
    1550          21 :                 break;
    1551             : 
    1552           1 :             case FGSO_GE:
    1553           1 :                 if (iFirstPageIdx[iLevel] < 0)
    1554             :                 {
    1555           1 :                     if (nComp <= 0)
    1556             :                     {
    1557           1 :                         iFirstPageIdx[iLevel] = static_cast<int>(i);
    1558           1 :                         iLastPageIdx[iLevel] = nSubPagesCount[iLevel];
    1559           1 :                         bStop = TRUE;
    1560             :                     }
    1561             :                 }
    1562           1 :                 break;
    1563             : 
    1564           1 :             case FGSO_GT:
    1565           1 :                 if (iFirstPageIdx[iLevel] < 0)
    1566             :                 {
    1567           1 :                     if (nComp < 0)
    1568             :                     {
    1569           1 :                         iFirstPageIdx[iLevel] = static_cast<int>(i);
    1570           1 :                         iLastPageIdx[iLevel] = nSubPagesCount[iLevel];
    1571           1 :                         bStop = TRUE;
    1572             :                     }
    1573             :                 }
    1574           1 :                 break;
    1575             : 
    1576           0 :             case FGSO_ISNOTNULL:
    1577           0 :                 CPLAssert(false);
    1578             :                 break;
    1579             :         }
    1580          26 :         if (bStop)
    1581           4 :             break;
    1582             :     }
    1583             : 
    1584          24 :     if (iFirstPageIdx[iLevel] < 0)
    1585             :     {
    1586           8 :         iFirstPageIdx[iLevel] = iLastPageIdx[iLevel] = nSubPagesCount[iLevel];
    1587             :     }
    1588          16 :     else if (iLastPageIdx[iLevel] < static_cast<int>(nSubPagesCount[iLevel]))
    1589             :     {
    1590          14 :         iLastPageIdx[iLevel]++;
    1591             :     }
    1592             : 
    1593          24 :     return true;
    1594             : }
    1595             : 
    1596             : /************************************************************************/
    1597             : /*                             Reset()                                  */
    1598             : /************************************************************************/
    1599             : 
    1600        8519 : void FileGDBIndexIteratorBase::Reset()
    1601             : {
    1602        8519 :     iCurPageIdx[0] = (bAscending) ? iFirstPageIdx[0] - 1 : iLastPageIdx[0] + 1;
    1603        8519 :     memset(iFirstPageIdx + 1, 0xFF, (MAX_DEPTH - 1) * sizeof(iFirstPageIdx[0]));
    1604        8519 :     memset(iLastPageIdx + 1, 0xFF, (MAX_DEPTH - 1) * sizeof(iLastPageIdx[0]));
    1605        8519 :     memset(iCurPageIdx + 1, 0xFF, (MAX_DEPTH - 1) * sizeof(iCurPageIdx[0]));
    1606        8519 :     memset(nLastPageAccessed, 0, MAX_DEPTH * sizeof(nLastPageAccessed[0]));
    1607        8519 :     iCurFeatureInPage = 0;
    1608        8519 :     nFeaturesInPage = 0;
    1609             : 
    1610        8519 :     bEOF = (m_nValueCountInIdx == 0);
    1611        8519 : }
    1612             : 
    1613             : /************************************************************************/
    1614             : /*                             Reset()                                  */
    1615             : /************************************************************************/
    1616             : 
    1617        1276 : void FileGDBIndexIterator::Reset()
    1618             : {
    1619        1276 :     FileGDBIndexIteratorBase::Reset();
    1620        1276 :     iSorted = 0;
    1621        1276 :     bEOF = bEOF || bEvaluateToFALSE;
    1622        1276 : }
    1623             : 
    1624             : /************************************************************************/
    1625             : /*                           ReadPageNumber()                           */
    1626             : /************************************************************************/
    1627             : 
    1628        7596 : uint64_t FileGDBIndexIteratorBase::ReadPageNumber(int iLevel)
    1629             : {
    1630        7596 :     const int errorRetValue = 0;
    1631             :     uint64_t nPage;
    1632        7596 :     if (m_nVersion == 1)
    1633             :     {
    1634        7596 :         nPage = GetUInt32(abyPage[iLevel] + m_nNonLeafPageHeaderSize,
    1635             :                           iCurPageIdx[iLevel]);
    1636        7596 :         if (nPage == nLastPageAccessed[iLevel])
    1637             :         {
    1638           1 :             if (!LoadNextPage(iLevel))
    1639           0 :                 return 0;
    1640           1 :             nPage = GetUInt32(abyPage[iLevel] + m_nNonLeafPageHeaderSize,
    1641             :                               iCurPageIdx[iLevel]);
    1642             :         }
    1643             :     }
    1644             :     else
    1645             :     {
    1646           0 :         nPage = GetUInt64(abyPage[iLevel] + m_nNonLeafPageHeaderSize,
    1647             :                           iCurPageIdx[iLevel]);
    1648           0 :         if (nPage == nLastPageAccessed[iLevel])
    1649             :         {
    1650           0 :             if (!LoadNextPage(iLevel))
    1651           0 :                 return 0;
    1652           0 :             nPage = GetUInt64(abyPage[iLevel] + m_nNonLeafPageHeaderSize,
    1653             :                               iCurPageIdx[iLevel]);
    1654             :         }
    1655             :     }
    1656        7596 :     nLastPageAccessed[iLevel] = nPage;
    1657        7596 :     returnErrorIf(nPage < 2);
    1658        7596 :     return nPage;
    1659             : }
    1660             : 
    1661             : /************************************************************************/
    1662             : /*                           LoadNextPage()                             */
    1663             : /************************************************************************/
    1664             : 
    1665        7628 : bool FileGDBIndexIteratorBase::LoadNextPage(int iLevel)
    1666             : {
    1667        7628 :     const bool errorRetValue = false;
    1668        7628 :     if ((bAscending && iCurPageIdx[iLevel] == iLastPageIdx[iLevel]) ||
    1669        4244 :         (!bAscending && iCurPageIdx[iLevel] == iFirstPageIdx[iLevel]))
    1670             :     {
    1671        3384 :         if (iLevel == 0 || !LoadNextPage(iLevel - 1))
    1672          31 :             return false;
    1673             : 
    1674        3353 :         const auto nPage = ReadPageNumber(iLevel - 1);
    1675        3353 :         returnErrorIf(!FindPages(iLevel, nPage));
    1676             : 
    1677        3353 :         iCurPageIdx[iLevel] =
    1678        3353 :             (bAscending) ? iFirstPageIdx[iLevel] : iLastPageIdx[iLevel];
    1679             :     }
    1680             :     else
    1681             :     {
    1682        4244 :         if (bAscending)
    1683        4243 :             iCurPageIdx[iLevel]++;
    1684             :         else
    1685           1 :             iCurPageIdx[iLevel]--;
    1686             :     }
    1687             : 
    1688        7597 :     return true;
    1689             : }
    1690             : 
    1691             : /************************************************************************/
    1692             : /*                        LoadNextFeaturePage()                         */
    1693             : /************************************************************************/
    1694             : 
    1695        6815 : bool FileGDBIndexIteratorBase::LoadNextFeaturePage()
    1696             : {
    1697        6815 :     const bool errorRetValue = false;
    1698             :     GUInt64 nPage;
    1699             : 
    1700        6815 :     if (nIndexDepth == 1)
    1701             :     {
    1702        2542 :         if (iCurPageIdx[0] == iLastPageIdx[0])
    1703             :         {
    1704        1022 :             return false;
    1705             :         }
    1706        1520 :         if (bAscending)
    1707        1484 :             iCurPageIdx[0]++;
    1708             :         else
    1709          36 :             iCurPageIdx[0]--;
    1710        1520 :         nPage = 1;
    1711             :     }
    1712             :     else
    1713             :     {
    1714        4273 :         if (!LoadNextPage(nIndexDepth - 2))
    1715             :         {
    1716          30 :             return false;
    1717             :         }
    1718        4243 :         nPage = ReadPageNumber(nIndexDepth - 2);
    1719        4243 :         returnErrorIf(nPage < 2);
    1720             :     }
    1721             : 
    1722             :     const cpl::NonCopyableVector<GByte> *cachedPagePtr =
    1723        5763 :         m_oCacheFeaturePage.getPtr(nPage);
    1724        5763 :     if (cachedPagePtr)
    1725             :     {
    1726        5115 :         memcpy(abyPageFeature, cachedPagePtr->data(), m_nPageSize);
    1727             :     }
    1728             :     else
    1729             :     {
    1730         648 :         cpl::NonCopyableVector<GByte> cachedPage;
    1731         648 :         if (m_oCacheFeaturePage.size() == m_oCacheFeaturePage.getMaxSize())
    1732             :         {
    1733         133 :             m_oCacheFeaturePage.removeAndRecycleOldestEntry(cachedPage);
    1734         133 :             cachedPage.clear();
    1735             :         }
    1736             : 
    1737         648 :         VSIFSeekL(fpCurIdx, static_cast<vsi_l_offset>(nPage - 1) * m_nPageSize,
    1738             :                   SEEK_SET);
    1739             : #ifdef DEBUG
    1740         648 :         iLoadedPage[nIndexDepth - 1] = nPage;
    1741             : #endif
    1742         648 :         returnErrorIf(VSIFReadL(abyPageFeature, m_nPageSize, 1, fpCurIdx) != 1);
    1743           0 :         cachedPage.insert(cachedPage.end(), abyPageFeature,
    1744         647 :                           abyPageFeature + m_nPageSize);
    1745         647 :         m_oCacheFeaturePage.insert(nPage, std::move(cachedPage));
    1746             :     }
    1747             : 
    1748        5762 :     const GUInt32 nFeatures = GetUInt32(abyPageFeature + m_nObjectIDSize, 0);
    1749        5762 :     returnErrorIf(nFeatures > nMaxPerPages);
    1750             : 
    1751        5761 :     nFeaturesInPage = static_cast<int>(nFeatures);
    1752        5761 :     iCurFeatureInPage = (bAscending) ? 0 : nFeaturesInPage - 1;
    1753        5761 :     return nFeatures != 0;
    1754             : }
    1755             : 
    1756             : /************************************************************************/
    1757             : /*                              GetNextRow()                            */
    1758             : /************************************************************************/
    1759             : 
    1760       16879 : int64_t FileGDBIndexIterator::GetNextRow()
    1761             : {
    1762       16879 :     const int64_t errorRetValue = -1;
    1763       16879 :     if (bEOF)
    1764          38 :         return -1;
    1765             : 
    1766             :     while (true)
    1767             :     {
    1768       19636 :         if (iCurFeatureInPage >= nFeaturesInPage || iCurFeatureInPage < 0)
    1769             :         {
    1770        1052 :             if (!LoadNextFeaturePage())
    1771             :             {
    1772         279 :                 bEOF = true;
    1773       16841 :                 return -1;
    1774             :             }
    1775             :         }
    1776             : 
    1777       19357 :         bool bMatch = false;
    1778       19357 :         if (eOp == FGSO_ISNOTNULL)
    1779             :         {
    1780       12843 :             bMatch = true;
    1781             :         }
    1782             :         else
    1783             :         {
    1784        6514 :             int nComp = 0;
    1785        6514 :             switch (eFieldType)
    1786             :             {
    1787          45 :                 case FGFT_INT16:
    1788             :                 {
    1789             :                     const GInt16 nVal =
    1790          45 :                         GetInt16(abyPageFeature + m_nOffsetFirstValInPage,
    1791             :                                  iCurFeatureInPage);
    1792          45 :                     nComp = COMPARE(sValue.Integer, nVal);
    1793          45 :                     break;
    1794             :                 }
    1795             : 
    1796         530 :                 case FGFT_INT32:
    1797             :                 {
    1798             :                     const GInt32 nVal =
    1799         530 :                         GetInt32(abyPageFeature + m_nOffsetFirstValInPage,
    1800             :                                  iCurFeatureInPage);
    1801         530 :                     nComp = COMPARE(sValue.Integer, nVal);
    1802         530 :                     break;
    1803             :                 }
    1804             : 
    1805          95 :                 case FGFT_FLOAT32:
    1806             :                 {
    1807             :                     const float fVal =
    1808          95 :                         GetFloat32(abyPageFeature + m_nOffsetFirstValInPage,
    1809             :                                    iCurFeatureInPage);
    1810          95 :                     nComp = COMPARE(sValue.Real, fVal);
    1811          95 :                     break;
    1812             :                 }
    1813             : 
    1814        4160 :                 case FGFT_FLOAT64:
    1815             :                 {
    1816             :                     const double dfVal =
    1817        4160 :                         GetFloat64(abyPageFeature + m_nOffsetFirstValInPage,
    1818             :                                    iCurFeatureInPage);
    1819        4160 :                     nComp = COMPARE(sValue.Real, dfVal);
    1820        4160 :                     break;
    1821             :                 }
    1822             : 
    1823          55 :                 case FGFT_DATETIME:
    1824             :                 case FGFT_DATE:
    1825             :                 case FGFT_TIME:
    1826             :                 case FGFT_DATETIME_WITH_OFFSET:
    1827             :                 {
    1828             :                     const double dfVal =
    1829          55 :                         GetFloat64(abyPageFeature + m_nOffsetFirstValInPage,
    1830             :                                    iCurFeatureInPage);
    1831          55 :                     if (sValue.Real + 1e-10 < dfVal)
    1832           0 :                         nComp = -1;
    1833          55 :                     else if (sValue.Real - 1e-10 > dfVal)
    1834          10 :                         nComp = 1;
    1835             :                     else
    1836          45 :                         nComp = 0;
    1837          55 :                     break;
    1838             :                 }
    1839             : 
    1840        1569 :                 case FGFT_STRING:
    1841             :                 {
    1842             :                     GUInt16 asVal[MAX_CAR_COUNT_INDEXED_STR];
    1843        1569 :                     memcpy(asVal,
    1844        1569 :                            abyPageFeature + m_nOffsetFirstValInPage +
    1845        1569 :                                nStrLen * 2 * iCurFeatureInPage,
    1846        1569 :                            nStrLen * 2);
    1847       38786 :                     for (int j = 0; j < nStrLen; j++)
    1848       37217 :                         CPL_LSBPTR16(&asVal[j]);
    1849             :                     // Note: we have an inconsistency. OGR SQL equality operator
    1850             :                     // is advertized to be case insensitive, but we have always
    1851             :                     // implemented FGSO_EQ as case sensitive.
    1852        1569 :                     nComp = FileGDBUTF16StrCompare(asUTF16Str, asVal, nStrLen,
    1853        1569 :                                                    eOp == FGSO_ILIKE);
    1854        1569 :                     break;
    1855             :                 }
    1856             : 
    1857          52 :                 case FGFT_GUID:
    1858             :                 case FGFT_GLOBALID:
    1859             :                 {
    1860          52 :                     nComp = memcmp(szUUID,
    1861          52 :                                    abyPageFeature + m_nOffsetFirstValInPage +
    1862          52 :                                        UUID_LEN_AS_STRING * iCurFeatureInPage,
    1863             :                                    UUID_LEN_AS_STRING);
    1864          52 :                     break;
    1865             :                 }
    1866             : 
    1867           8 :                 case FGFT_INT64:
    1868             :                 {
    1869             :                     const int64_t nVal =
    1870           8 :                         GetInt64(abyPageFeature + m_nOffsetFirstValInPage,
    1871             :                                  iCurFeatureInPage);
    1872           8 :                     nComp = COMPARE(sValue.Integer64, nVal);
    1873           8 :                     break;
    1874             :                 }
    1875             : 
    1876           0 :                 default:
    1877           0 :                     CPLAssert(false);
    1878             :                     nComp = 0;
    1879             :                     break;
    1880             :             }
    1881             : 
    1882        6514 :             bMatch = false;
    1883        6514 :             CPL_IGNORE_RET_VAL(bMatch);
    1884        6514 :             switch (eOp)
    1885             :             {
    1886         909 :                 case FGSO_LT:
    1887         909 :                     if (nComp <= 0 && bAscending)
    1888             :                     {
    1889          34 :                         bEOF = true;
    1890          34 :                         return -1;
    1891             :                     }
    1892         875 :                     bMatch = true;
    1893         875 :                     break;
    1894             : 
    1895         126 :                 case FGSO_LE:
    1896         126 :                     if (nComp < 0 && bAscending)
    1897             :                     {
    1898          12 :                         bEOF = true;
    1899          12 :                         return -1;
    1900             :                     }
    1901         114 :                     bMatch = true;
    1902         114 :                     break;
    1903             : 
    1904        4136 :                 case FGSO_EQ:
    1905             :                 case FGSO_ILIKE:
    1906        4136 :                     if (nComp < 0 && bAscending)
    1907             :                     {
    1908         137 :                         bEOF = true;
    1909         137 :                         return -1;
    1910             :                     }
    1911        3999 :                     bMatch = nComp == 0;
    1912        3999 :                     break;
    1913             : 
    1914         848 :                 case FGSO_GE:
    1915         848 :                     bMatch = nComp <= 0;
    1916         848 :                     break;
    1917             : 
    1918         495 :                 case FGSO_GT:
    1919         495 :                     bMatch = nComp < 0;
    1920         495 :                     break;
    1921             : 
    1922           0 :                 case FGSO_ISNOTNULL:
    1923           0 :                     CPLAssert(false);
    1924             :                     break;
    1925             :             }
    1926             :         }
    1927             : 
    1928       19174 :         if (bMatch)
    1929             :         {
    1930             :             const GUInt64 nFID =
    1931       16379 :                 m_nVersion == 1
    1932       16379 :                     ? GetUInt32(abyPageFeature + m_nLeafPageHeaderSize,
    1933             :                                 iCurFeatureInPage)
    1934           0 :                     : GetUInt64(abyPageFeature + m_nLeafPageHeaderSize,
    1935       16379 :                                 iCurFeatureInPage);
    1936       16379 :             if (bAscending)
    1937       16232 :                 iCurFeatureInPage++;
    1938             :             else
    1939         147 :                 iCurFeatureInPage--;
    1940       16379 :             returnErrorAndCleanupIf(
    1941             :                 nFID < 1 || nFID > static_cast<GUInt64>(
    1942             :                                        poParent->GetTotalRecordCount()),
    1943             :                 bEOF = true);
    1944       16379 :             return static_cast<int64_t>(nFID - 1);
    1945             :         }
    1946             :         else
    1947             :         {
    1948        2795 :             if (bAscending)
    1949        2795 :                 iCurFeatureInPage++;
    1950             :             else
    1951           0 :                 iCurFeatureInPage--;
    1952             :         }
    1953        2795 :     }
    1954             : }
    1955             : 
    1956             : /************************************************************************/
    1957             : /*                             SortRows()                               */
    1958             : /************************************************************************/
    1959             : 
    1960          88 : int FileGDBIndexIterator::SortRows()
    1961             : {
    1962          88 :     nSortedCount = 0;
    1963          88 :     iSorted = 0;
    1964          88 :     int nSortedAlloc = 0;
    1965          88 :     Reset();
    1966             :     while (true)
    1967             :     {
    1968        1326 :         int64_t nRow = GetNextRow();
    1969        1326 :         if (nRow < 0)
    1970          88 :             break;
    1971        1238 :         if (nSortedCount == nSortedAlloc)
    1972             :         {
    1973          87 :             int nNewSortedAlloc = 4 * nSortedAlloc / 3 + 16;
    1974             :             int64_t *panNewSortedRows =
    1975          87 :                 static_cast<int64_t *>(VSI_REALLOC_VERBOSE(
    1976             :                     panSortedRows, sizeof(int64_t) * nNewSortedAlloc));
    1977          87 :             if (panNewSortedRows == nullptr)
    1978             :             {
    1979           0 :                 nSortedCount = 0;
    1980           0 :                 return FALSE;
    1981             :             }
    1982          87 :             nSortedAlloc = nNewSortedAlloc;
    1983          87 :             panSortedRows = panNewSortedRows;
    1984             :         }
    1985        1238 :         panSortedRows[nSortedCount++] = nRow;
    1986        1238 :     }
    1987          88 :     if (nSortedCount == 0)
    1988          25 :         return FALSE;
    1989          63 :     std::sort(panSortedRows, panSortedRows + nSortedCount);
    1990             : #ifdef m_nValueCountInIdx_reliable
    1991             :     if (eOp == FGSO_ISNOTNULL && (int64_t)m_nValueCountInIdx != nSortedCount)
    1992             :         PrintError();
    1993             : #endif
    1994          63 :     return TRUE;
    1995             : }
    1996             : 
    1997             : /************************************************************************/
    1998             : /*                        GetNextRowSortedByFID()                       */
    1999             : /************************************************************************/
    2000             : 
    2001        2807 : int64_t FileGDBIndexIterator::GetNextRowSortedByFID()
    2002             : {
    2003        2807 :     if (eOp == FGSO_EQ)
    2004        1040 :         return GetNextRow();
    2005             : 
    2006        1767 :     if (iSorted < nSortedCount)
    2007        1586 :         return panSortedRows[iSorted++];
    2008             : 
    2009         181 :     if (nSortedCount < 0)
    2010             :     {
    2011          88 :         if (!SortRows())
    2012          25 :             return -1;
    2013          63 :         return panSortedRows[iSorted++];
    2014             :     }
    2015             :     else
    2016             :     {
    2017          93 :         return -1;
    2018             :     }
    2019             : }
    2020             : 
    2021             : /************************************************************************/
    2022             : /*                           GetRowCount()                              */
    2023             : /************************************************************************/
    2024             : 
    2025         208 : int64_t FileGDBIndexIterator::GetRowCount()
    2026             : {
    2027             :     // The m_nValueCountInIdx value has been found to be unreliable when the index
    2028             :     // is built as features are inserted (and when they are not in increasing
    2029             :     // order) (with FileGDB SDK 1.3) So disable this optimization as there's no
    2030             :     // fast way to know if the value is reliable or not.
    2031             : #ifdef m_nValueCountInIdx_reliable
    2032             :     if (eOp == FGSO_ISNOTNULL)
    2033             :         return (int64_t)m_nValueCountInIdx;
    2034             : #endif
    2035             : 
    2036         208 :     if (nSortedCount >= 0)
    2037           0 :         return nSortedCount;
    2038             : 
    2039         208 :     int64_t nRowCount = 0;
    2040         208 :     bool bSaveAscending = bAscending;
    2041         208 :     bAscending = true; /* for a tiny bit of more efficiency */
    2042         208 :     Reset();
    2043       13756 :     while (GetNextRow() >= 0)
    2044       13548 :         nRowCount++;
    2045         208 :     bAscending = bSaveAscending;
    2046         208 :     Reset();
    2047         208 :     return nRowCount;
    2048             : }
    2049             : 
    2050             : /************************************************************************/
    2051             : /*                            GetMinMaxValue()                          */
    2052             : /************************************************************************/
    2053             : 
    2054          50 : const OGRField *FileGDBIndexIterator::GetMinMaxValue(OGRField *psField,
    2055             :                                                      int &eOutType, int bIsMin)
    2056             : {
    2057          50 :     const OGRField *errorRetValue = nullptr;
    2058          50 :     eOutType = -1;
    2059          50 :     if (m_nValueCountInIdx == 0)
    2060           2 :         return nullptr;
    2061             : 
    2062          96 :     std::vector<GByte> l_abyPageV;
    2063             :     try
    2064             :     {
    2065          48 :         l_abyPageV.resize(m_nPageSize);
    2066             :     }
    2067           0 :     catch (const std::exception &)
    2068             :     {
    2069           0 :         return nullptr;
    2070             :     }
    2071          48 :     GByte *l_abyPage = l_abyPageV.data();
    2072          48 :     uint64_t nPage = 1;
    2073          52 :     for (GUInt32 iLevel = 0; iLevel < nIndexDepth - 1; iLevel++)
    2074             :     {
    2075           4 :         VSIFSeekL(fpCurIdx, static_cast<vsi_l_offset>(nPage - 1) * m_nPageSize,
    2076             :                   SEEK_SET);
    2077             : #ifdef DEBUG
    2078           4 :         iLoadedPage[iLevel] = nPage;
    2079             : #endif
    2080           4 :         returnErrorIf(VSIFReadL(l_abyPage, m_nPageSize, 1, fpCurIdx) != 1);
    2081           4 :         GUInt32 l_nSubPagesCount = GetUInt32(l_abyPage + m_nObjectIDSize, 0);
    2082           4 :         returnErrorIf(l_nSubPagesCount == 0 || l_nSubPagesCount > nMaxPerPages);
    2083             : 
    2084           4 :         if (m_nVersion == 1)
    2085             :         {
    2086           4 :             nPage = GetUInt32(l_abyPage + m_nNonLeafPageHeaderSize,
    2087             :                               bIsMin ? 0 : l_nSubPagesCount);
    2088             :         }
    2089             :         else
    2090             :         {
    2091           0 :             nPage = GetUInt64(l_abyPage + m_nNonLeafPageHeaderSize,
    2092             :                               bIsMin ? 0 : l_nSubPagesCount);
    2093             :         }
    2094           4 :         returnErrorIf(nPage < 2);
    2095             :     }
    2096             : 
    2097          48 :     VSIFSeekL(fpCurIdx, static_cast<vsi_l_offset>(nPage - 1) * m_nPageSize,
    2098             :               SEEK_SET);
    2099             : #ifdef DEBUG
    2100          48 :     iLoadedPage[nIndexDepth - 1] = nPage;
    2101             : #endif
    2102          48 :     returnErrorIf(VSIFReadL(l_abyPage, m_nPageSize, 1, fpCurIdx) != 1);
    2103             : 
    2104          48 :     GUInt32 nFeatures = GetUInt32(l_abyPage + m_nObjectIDSize, 0);
    2105          48 :     returnErrorIf(nFeatures < 1 || nFeatures > nMaxPerPages);
    2106             : 
    2107          48 :     int iFeature = (bIsMin) ? 0 : nFeatures - 1;
    2108             : 
    2109          48 :     switch (eFieldType)
    2110             :     {
    2111           1 :         case FGFT_INT16:
    2112             :         {
    2113             :             const GInt16 nVal =
    2114           1 :                 GetInt16(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2115           1 :             psField->Integer = nVal;
    2116           1 :             eOutType = OFTInteger;
    2117           1 :             return psField;
    2118             :         }
    2119             : 
    2120           2 :         case FGFT_INT32:
    2121             :         {
    2122             :             const GInt32 nVal =
    2123           2 :                 GetInt32(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2124           2 :             psField->Integer = nVal;
    2125           2 :             eOutType = OFTInteger;
    2126           2 :             return psField;
    2127             :         }
    2128             : 
    2129           1 :         case FGFT_FLOAT32:
    2130             :         {
    2131             :             const float fVal =
    2132           1 :                 GetFloat32(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2133           1 :             psField->Real = fVal;
    2134           1 :             eOutType = OFTReal;
    2135           1 :             return psField;
    2136             :         }
    2137             : 
    2138           1 :         case FGFT_FLOAT64:
    2139             :         {
    2140             :             const double dfVal =
    2141           1 :                 GetFloat64(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2142           1 :             psField->Real = dfVal;
    2143           1 :             eOutType = OFTReal;
    2144           1 :             return psField;
    2145             :         }
    2146             : 
    2147           5 :         case FGFT_DATETIME:
    2148             :         case FGFT_DATETIME_WITH_OFFSET:
    2149             :         {
    2150             :             const double dfVal =
    2151           5 :                 GetFloat64(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2152           5 :             FileGDBDoubleDateToOGRDate(dfVal, false, psField);
    2153           5 :             eOutType = OFTDateTime;
    2154           5 :             return psField;
    2155             :         }
    2156             : 
    2157           2 :         case FGFT_DATE:
    2158             :         {
    2159             :             const double dfVal =
    2160           2 :                 GetFloat64(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2161           2 :             FileGDBDoubleDateToOGRDate(dfVal, false, psField);
    2162           2 :             eOutType = OFTDate;
    2163           2 :             return psField;
    2164             :         }
    2165             : 
    2166           2 :         case FGFT_TIME:
    2167             :         {
    2168             :             const double dfVal =
    2169           2 :                 GetFloat64(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2170           2 :             FileGDBDoubleTimeToOGRTime(dfVal, psField);
    2171           2 :             eOutType = OFTTime;
    2172           2 :             return psField;
    2173             :         }
    2174             : 
    2175          29 :         case FGFT_STRING:
    2176             :         {
    2177          29 :             wchar_t awsVal[MAX_CAR_COUNT_INDEXED_STR + 1] = {0};
    2178         725 :             for (int j = 0; j < nStrLen; j++)
    2179             :             {
    2180             :                 GUInt16 nCh =
    2181         696 :                     GetUInt16(l_abyPage + m_nOffsetFirstValInPage +
    2182         696 :                                   nStrLen * sizeof(GUInt16) * iFeature,
    2183             :                               j);
    2184         696 :                 awsVal[j] = nCh;
    2185             :             }
    2186          29 :             awsVal[nStrLen] = 0;
    2187             :             char *pszOut =
    2188          29 :                 CPLRecodeFromWChar(awsVal, CPL_ENC_UCS2, CPL_ENC_UTF8);
    2189          29 :             returnErrorIf(pszOut == nullptr);
    2190          29 :             returnErrorAndCleanupIf(strlen(pszOut) >
    2191             :                                         static_cast<size_t>(MAX_UTF8_LEN_STR),
    2192             :                                     VSIFree(pszOut));
    2193          29 :             strcpy(psField->String, pszOut);
    2194          29 :             CPLFree(pszOut);
    2195          29 :             eOutType = OFTString;
    2196          29 :             return psField;
    2197             :         }
    2198             : 
    2199           1 :         case FGFT_GUID:
    2200             :         case FGFT_GLOBALID:
    2201             :         {
    2202           1 :             memcpy(psField->String,
    2203           1 :                    l_abyPage + m_nOffsetFirstValInPage +
    2204           1 :                        UUID_LEN_AS_STRING * iFeature,
    2205             :                    UUID_LEN_AS_STRING);
    2206           1 :             psField->String[UUID_LEN_AS_STRING] = 0;
    2207           1 :             eOutType = OFTString;
    2208           1 :             return psField;
    2209             :         }
    2210             : 
    2211           4 :         case FGFT_INT64:
    2212             :         {
    2213             :             const int64_t nVal =
    2214           4 :                 GetInt64(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2215           4 :             psField->Integer64 = nVal;
    2216           4 :             eOutType = OFTInteger64;
    2217           4 :             return psField;
    2218             :         }
    2219             : 
    2220           0 :         default:
    2221           0 :             CPLAssert(false);
    2222             :             break;
    2223             :     }
    2224             :     return nullptr;
    2225             : }
    2226             : 
    2227             : /************************************************************************/
    2228             : /*                            GetMinValue()                             */
    2229             : /************************************************************************/
    2230             : 
    2231          14 : const OGRField *FileGDBIndexIterator::GetMinValue(int &eOutType)
    2232             : {
    2233          14 :     if (eOp != FGSO_ISNOTNULL)
    2234           0 :         return FileGDBIterator::GetMinValue(eOutType);
    2235          14 :     if (eFieldType == FGFT_STRING || eFieldType == FGFT_GUID ||
    2236          12 :         eFieldType == FGFT_GLOBALID)
    2237           2 :         sMin.String = szMin;
    2238          14 :     return GetMinMaxValue(&sMin, eOutType, TRUE);
    2239             : }
    2240             : 
    2241             : /************************************************************************/
    2242             : /*                            GetMaxValue()                             */
    2243             : /************************************************************************/
    2244             : 
    2245          36 : const OGRField *FileGDBIndexIterator::GetMaxValue(int &eOutType)
    2246             : {
    2247          36 :     if (eOp != FGSO_ISNOTNULL)
    2248           0 :         return FileGDBIterator::GetMinValue(eOutType);
    2249          36 :     if (eFieldType == FGFT_STRING || eFieldType == FGFT_GUID ||
    2250           7 :         eFieldType == FGFT_GLOBALID)
    2251          29 :         sMax.String = szMax;
    2252          36 :     return GetMinMaxValue(&sMax, eOutType, FALSE);
    2253             : }
    2254             : 
    2255             : /************************************************************************/
    2256             : /*                        GetMinMaxSumCount()                           */
    2257             : /************************************************************************/
    2258             : 
    2259             : struct Int16Getter
    2260             : {
    2261             :   public:
    2262           5 :     static double GetAsDouble(const GByte *pBaseAddr, int iOffset)
    2263             :     {
    2264           5 :         return GetInt16(pBaseAddr, iOffset);
    2265             :     }
    2266             : };
    2267             : 
    2268             : struct Int32Getter
    2269             : {
    2270             :   public:
    2271          15 :     static double GetAsDouble(const GByte *pBaseAddr, int iOffset)
    2272             :     {
    2273          15 :         return GetInt32(pBaseAddr, iOffset);
    2274             :     }
    2275             : };
    2276             : 
    2277             : struct Int64Getter
    2278             : {
    2279             :   public:
    2280           0 :     static double GetAsDouble(const GByte *pBaseAddr, int iOffset)
    2281             :     {
    2282           0 :         return static_cast<double>(GetInt64(pBaseAddr, iOffset));
    2283             :     }
    2284             : };
    2285             : 
    2286             : struct Float32Getter
    2287             : {
    2288             :   public:
    2289           5 :     static double GetAsDouble(const GByte *pBaseAddr, int iOffset)
    2290             :     {
    2291           5 :         return GetFloat32(pBaseAddr, iOffset);
    2292             :     }
    2293             : };
    2294             : 
    2295             : struct Float64Getter
    2296             : {
    2297             :   public:
    2298          10 :     static double GetAsDouble(const GByte *pBaseAddr, int iOffset)
    2299             :     {
    2300          10 :         return GetFloat64(pBaseAddr, iOffset);
    2301             :     }
    2302             : };
    2303             : 
    2304             : template <class Getter>
    2305           8 : void FileGDBIndexIterator::GetMinMaxSumCount(double &dfMin, double &dfMax,
    2306             :                                              double &dfSum, int &nCount)
    2307             : {
    2308           8 :     int nLocalCount = 0;
    2309           8 :     double dfLocalSum = 0.0;
    2310           8 :     double dfVal = 0.0;
    2311             : 
    2312             :     while (true)
    2313             :     {
    2314          43 :         if (iCurFeatureInPage >= nFeaturesInPage)
    2315             :         {
    2316          15 :             if (!LoadNextFeaturePage())
    2317             :             {
    2318           8 :                 break;
    2319             :             }
    2320             :         }
    2321             : 
    2322          35 :         dfVal = Getter::GetAsDouble(abyPageFeature + m_nOffsetFirstValInPage,
    2323             :                                     iCurFeatureInPage);
    2324             : 
    2325          35 :         dfLocalSum += dfVal;
    2326          35 :         if (nLocalCount == 0)
    2327           7 :             dfMin = dfVal;
    2328          35 :         nLocalCount++;
    2329          35 :         iCurFeatureInPage++;
    2330             :     }
    2331             : 
    2332           8 :     dfSum = dfLocalSum;
    2333           8 :     nCount = nLocalCount;
    2334           8 :     dfMax = dfVal;
    2335           8 : }
    2336             : 
    2337           8 : bool FileGDBIndexIterator::GetMinMaxSumCount(double &dfMin, double &dfMax,
    2338             :                                              double &dfSum, int &nCount)
    2339             : {
    2340           8 :     const bool errorRetValue = false;
    2341           8 :     dfMin = 0.0;
    2342           8 :     dfMax = 0.0;
    2343           8 :     dfSum = 0.0;
    2344           8 :     nCount = 0;
    2345           8 :     returnErrorIf(eOp != FGSO_ISNOTNULL);
    2346           8 :     returnErrorIf(eFieldType != FGFT_INT16 && eFieldType != FGFT_INT32 &&
    2347             :                   eFieldType != FGFT_FLOAT32 && eFieldType != FGFT_FLOAT64 &&
    2348             :                   eFieldType != FGFT_DATETIME && eFieldType != FGFT_INT64 &&
    2349             :                   eFieldType != FGFT_DATE && eFieldType != FGFT_TIME &&
    2350             :                   eFieldType != FGFT_DATETIME_WITH_OFFSET);
    2351             : 
    2352           8 :     bool bSaveAscending = bAscending;
    2353           8 :     bAscending = true;
    2354           8 :     Reset();
    2355             : 
    2356           8 :     switch (eFieldType)
    2357             :     {
    2358           1 :         case FGFT_INT16:
    2359             :         {
    2360           1 :             GetMinMaxSumCount<Int16Getter>(dfMin, dfMax, dfSum, nCount);
    2361           1 :             break;
    2362             :         }
    2363           4 :         case FGFT_INT32:
    2364             :         {
    2365           4 :             GetMinMaxSumCount<Int32Getter>(dfMin, dfMax, dfSum, nCount);
    2366           4 :             break;
    2367             :         }
    2368           0 :         case FGFT_INT64:
    2369             :         {
    2370           0 :             GetMinMaxSumCount<Int64Getter>(dfMin, dfMax, dfSum, nCount);
    2371           0 :             break;
    2372             :         }
    2373           1 :         case FGFT_FLOAT32:
    2374             :         {
    2375           1 :             GetMinMaxSumCount<Float32Getter>(dfMin, dfMax, dfSum, nCount);
    2376           1 :             break;
    2377             :         }
    2378           2 :         case FGFT_FLOAT64:
    2379             :         case FGFT_DATETIME:
    2380             :         case FGFT_DATE:
    2381             :         case FGFT_TIME:
    2382             :         case FGFT_DATETIME_WITH_OFFSET:
    2383             :         {
    2384           2 :             GetMinMaxSumCount<Float64Getter>(dfMin, dfMax, dfSum, nCount);
    2385           2 :             break;
    2386             :         }
    2387           0 :         default:
    2388           0 :             CPLAssert(false);
    2389             :             break;
    2390             :     }
    2391             : 
    2392           8 :     bAscending = bSaveAscending;
    2393           8 :     Reset();
    2394             : 
    2395           8 :     return true;
    2396             : }
    2397             : 
    2398             : FileGDBSpatialIndexIterator::~FileGDBSpatialIndexIterator() = default;
    2399             : 
    2400             : /************************************************************************/
    2401             : /*                    FileGDBSpatialIndexIteratorImpl                   */
    2402             : /************************************************************************/
    2403             : 
    2404             : class FileGDBSpatialIndexIteratorImpl final : public FileGDBIndexIteratorBase,
    2405             :                                               public FileGDBSpatialIndexIterator
    2406             : {
    2407             :     OGREnvelope m_sFilterEnvelope;
    2408             :     bool m_bHasBuiltSetFID = false;
    2409             :     std::vector<int64_t> m_oFIDVector{};
    2410             :     size_t m_nVectorIdx = 0;
    2411             :     int m_nGridNo = 0;
    2412             :     GInt64 m_nMinVal = 0;
    2413             :     GInt64 m_nMaxVal = 0;
    2414             :     GInt32 m_nCurX = 0;
    2415             :     GInt32 m_nMaxX = 0;
    2416             : 
    2417             :     bool FindPages(int iLevel, uint64_t nPage) override;
    2418             :     int GetNextRow();
    2419             :     bool ReadNewXRange();
    2420             :     bool ResetInternal();
    2421             :     double GetScaledCoord(double coord) const;
    2422             : 
    2423             :   protected:
    2424             :     friend class FileGDBSpatialIndexIterator;
    2425             : 
    2426             :     FileGDBSpatialIndexIteratorImpl(FileGDBTable *poParent,
    2427             :                                     const OGREnvelope &sFilterEnvelope);
    2428             :     bool Init();
    2429             : 
    2430             :   public:
    2431           2 :     FileGDBTable *GetTable() override
    2432             :     {
    2433           2 :         return poParent;
    2434             :     }  // avoid MSVC C4250 inherits via dominance warning
    2435             : 
    2436             :     int64_t GetNextRowSortedByFID() override;
    2437             :     void Reset() override;
    2438             : 
    2439             :     bool SetEnvelope(const OGREnvelope &sFilterEnvelope) override;
    2440             : };
    2441             : 
    2442             : /************************************************************************/
    2443             : /*                      FileGDBSpatialIndexIteratorImpl()               */
    2444             : /************************************************************************/
    2445             : 
    2446         361 : FileGDBSpatialIndexIteratorImpl::FileGDBSpatialIndexIteratorImpl(
    2447         361 :     FileGDBTable *poParentIn, const OGREnvelope &sFilterEnvelope)
    2448             :     : FileGDBIndexIteratorBase(poParentIn, true),
    2449         361 :       m_sFilterEnvelope(sFilterEnvelope)
    2450             : {
    2451             :     double dfYMinClamped;
    2452             :     double dfYMaxClamped;
    2453         361 :     poParentIn->GetMinMaxProjYForSpatialIndex(dfYMinClamped, dfYMaxClamped);
    2454         361 :     m_sFilterEnvelope.MinY = std::min(
    2455         361 :         std::max(m_sFilterEnvelope.MinY, dfYMinClamped), dfYMaxClamped);
    2456         361 :     m_sFilterEnvelope.MaxY = std::min(
    2457         361 :         std::max(m_sFilterEnvelope.MaxY, dfYMinClamped), dfYMaxClamped);
    2458         361 : }
    2459             : 
    2460             : /************************************************************************/
    2461             : /*                                  Build()                             */
    2462             : /************************************************************************/
    2463             : 
    2464             : FileGDBSpatialIndexIterator *
    2465         361 : FileGDBSpatialIndexIterator::Build(FileGDBTable *poParent,
    2466             :                                    const OGREnvelope &sFilterEnvelope)
    2467             : {
    2468             :     FileGDBSpatialIndexIteratorImpl *poIterator =
    2469         361 :         new FileGDBSpatialIndexIteratorImpl(poParent, sFilterEnvelope);
    2470         361 :     if (!poIterator->Init())
    2471             :     {
    2472         252 :         delete poIterator;
    2473         252 :         return nullptr;
    2474             :     }
    2475         109 :     return poIterator;
    2476             : }
    2477             : 
    2478             : /************************************************************************/
    2479             : /*                         SetEnvelope()                                */
    2480             : /************************************************************************/
    2481             : 
    2482        1068 : bool FileGDBSpatialIndexIteratorImpl::SetEnvelope(
    2483             :     const OGREnvelope &sFilterEnvelope)
    2484             : {
    2485        1068 :     m_sFilterEnvelope = sFilterEnvelope;
    2486        1068 :     m_bHasBuiltSetFID = false;
    2487        1068 :     m_oFIDVector.clear();
    2488        1068 :     return ResetInternal();
    2489             : }
    2490             : 
    2491             : /************************************************************************/
    2492             : /*                              Init()                                  */
    2493             : /************************************************************************/
    2494             : 
    2495         361 : bool FileGDBSpatialIndexIteratorImpl::Init()
    2496             : {
    2497         361 :     const bool errorRetValue = false;
    2498             : 
    2499             :     const std::string osSpxName = CPLFormFilenameSafe(
    2500         722 :         CPLGetPathSafe(poParent->GetFilename().c_str()).c_str(),
    2501        1083 :         CPLGetBasenameSafe(poParent->GetFilename().c_str()).c_str(), "spx");
    2502             : 
    2503         361 :     if (!ReadTrailer(osSpxName.c_str()))
    2504         192 :         return false;
    2505             : 
    2506         169 :     returnErrorIf(m_nValueSize != sizeof(uint64_t));
    2507             : 
    2508         220 :     const auto IsPositiveInt = [](double x) { return x >= 0 && x <= INT_MAX; };
    2509             : 
    2510         169 :     const auto &gridRes = poParent->GetSpatialIndexGridResolution();
    2511         169 :     const FileGDBGeomField *poGDBGeomField = poParent->GetGeomField();
    2512         389 :     if (gridRes.empty() || !(gridRes[0] > 0) ||
    2513             :         // Check if the center of the layer extent results in valid scaled
    2514             :         // coords
    2515         110 :         !(!std::isnan(poGDBGeomField->GetXMin()) &&
    2516         110 :           IsPositiveInt(GetScaledCoord(
    2517         110 :               0.5 * (poGDBGeomField->GetXMin() + poGDBGeomField->GetXMax()))) &&
    2518         110 :           IsPositiveInt(GetScaledCoord(
    2519         110 :               0.5 * (poGDBGeomField->GetYMin() + poGDBGeomField->GetYMax())))))
    2520             :     {
    2521             :         // gridRes[0] == 1.61271680278378622e-312 happens on layer
    2522             :         // Zone18_2014_01_Broadcast of
    2523             :         // https://coast.noaa.gov/htdata/CMSP/AISDataHandler/2014/01/Zone18_2014_01.zip
    2524             :         // The FileGDB driver does not use the .spx file in that situation,
    2525             :         // so do we.
    2526          59 :         CPLDebug("OpenFileGDB",
    2527             :                  "Cannot use %s as the grid resolution is invalid",
    2528             :                  osSpxName.c_str());
    2529          59 :         return false;
    2530             :     }
    2531             : 
    2532             :     // Detect broken .spx file such as SWISSTLM3D_2022_LV95_LN02.gdb/a00000019.spx
    2533             :     // from https://data.geo.admin.ch/ch.swisstopo.swisstlm3d/swisstlm3d_2022-03/swisstlm3d_2022-03_2056_5728.gdb.zip
    2534             :     // which advertises nIndexDepth == 1 whereas it seems to be it should be 2.
    2535         110 :     if (nIndexDepth == 1)
    2536             :     {
    2537         108 :         iLastPageIdx[0] = 0;
    2538         108 :         LoadNextFeaturePage();
    2539         108 :         iFirstPageIdx[0] = iLastPageIdx[0] = -1;
    2540         323 :         if (nFeaturesInPage >= 2 &&
    2541         109 :             nFeaturesInPage < poParent->GetTotalRecordCount() / 10 &&
    2542           1 :             m_nPageCount > static_cast<GUInt32>(nFeaturesInPage))
    2543             :         {
    2544             :             // Check if it looks like a non-feature page, that is that the
    2545             :             // IDs pointed by it are index page IDs and not feature IDs.
    2546           1 :             bool bReferenceOtherPages = true;
    2547           8 :             for (int i = 0; i < nFeaturesInPage; ++i)
    2548             :             {
    2549           7 :                 const GUInt32 nID = GetUInt32(abyPageFeature + 8, i);
    2550           7 :                 if (!(nID >= 2 && nID <= m_nPageCount))
    2551             :                 {
    2552           0 :                     bReferenceOtherPages = false;
    2553           0 :                     break;
    2554             :                 }
    2555             :             }
    2556           1 :             if (bReferenceOtherPages)
    2557             :             {
    2558           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2559             :                          "Cannot use %s as the index depth(=1) is suspicious "
    2560             :                          "(it should rather be 2)",
    2561             :                          osSpxName.c_str());
    2562           1 :                 return false;
    2563             :             }
    2564             :         }
    2565             :     }
    2566             : 
    2567         109 :     return ResetInternal();
    2568             : }
    2569             : 
    2570             : /************************************************************************/
    2571             : /*                         GetScaledCoord()                             */
    2572             : /************************************************************************/
    2573             : 
    2574       22426 : double FileGDBSpatialIndexIteratorImpl::GetScaledCoord(double coord) const
    2575             : {
    2576       22426 :     const auto &gridRes = poParent->GetSpatialIndexGridResolution();
    2577       22426 :     return (coord / gridRes[0] + (1 << 29)) / (gridRes[m_nGridNo] / gridRes[0]);
    2578             : }
    2579             : 
    2580             : /************************************************************************/
    2581             : /*                         ReadNewXRange()                              */
    2582             : /************************************************************************/
    2583             : 
    2584        7243 : bool FileGDBSpatialIndexIteratorImpl::ReadNewXRange()
    2585             : {
    2586             :     const GUInt64 v1 =
    2587        7243 :         (static_cast<GUInt64>(m_nGridNo) << 62) |
    2588       14486 :         (static_cast<GUInt64>(m_nCurX) << 31) |
    2589        7243 :         (static_cast<GUInt64>(
    2590       21729 :             std::min(std::max(0.0, GetScaledCoord(m_sFilterEnvelope.MinY)),
    2591        7243 :                      static_cast<double>(INT_MAX))));
    2592             :     const GUInt64 v2 =
    2593        7243 :         (static_cast<GUInt64>(m_nGridNo) << 62) |
    2594       14486 :         (static_cast<GUInt64>(m_nCurX) << 31) |
    2595        7243 :         (static_cast<GUInt64>(
    2596       21729 :             std::min(std::max(0.0, GetScaledCoord(m_sFilterEnvelope.MaxY)),
    2597        7243 :                      static_cast<double>(INT_MAX))));
    2598        7243 :     if (m_nGridNo < 2)
    2599             :     {
    2600        7243 :         m_nMinVal = v1;
    2601        7243 :         m_nMaxVal = v2;
    2602             :     }
    2603             :     else
    2604             :     {
    2605             :         // Reverse order due to negative sign
    2606           0 :         memcpy(&m_nMinVal, &v2, sizeof(GInt64));
    2607           0 :         memcpy(&m_nMaxVal, &v1, sizeof(GInt64));
    2608             :     }
    2609             : 
    2610        7243 :     const bool errorRetValue = false;
    2611        7243 :     if (m_nValueCountInIdx > 0)
    2612             :     {
    2613        7243 :         if (nIndexDepth == 1)
    2614             :         {
    2615        2859 :             iFirstPageIdx[0] = iLastPageIdx[0] = 0;
    2616             :         }
    2617             :         else
    2618             :         {
    2619        4384 :             returnErrorIf(!FindPages(0, 1));
    2620             :         }
    2621             :     }
    2622             : 
    2623        7243 :     FileGDBIndexIteratorBase::Reset();
    2624             : 
    2625        7243 :     return true;
    2626             : }
    2627             : 
    2628             : /************************************************************************/
    2629             : /*                         FindMinMaxIdx()                              */
    2630             : /************************************************************************/
    2631             : 
    2632        4906 : static bool FindMinMaxIdx(const GByte *pBaseAddr, const int nVals,
    2633             :                           const GInt64 nMinVal, const GInt64 nMaxVal,
    2634             :                           int &minIdxOut, int &maxIdxOut)
    2635             : {
    2636             :     // Find maximum index that is <= nMaxVal
    2637        4906 :     int nMinIdx = 0;
    2638        4906 :     int nMaxIdx = nVals - 1;
    2639       41108 :     while (nMaxIdx - nMinIdx >= 2)
    2640             :     {
    2641       36202 :         int nIdx = (nMinIdx + nMaxIdx) / 2;
    2642       36202 :         const GInt64 nVal = GetInt64(pBaseAddr, nIdx);
    2643       36202 :         if (nVal <= nMaxVal)
    2644        5022 :             nMinIdx = nIdx;
    2645             :         else
    2646       31180 :             nMaxIdx = nIdx;
    2647             :     }
    2648        9401 :     while (GetInt64(pBaseAddr, nMaxIdx) > nMaxVal)
    2649             :     {
    2650        8244 :         nMaxIdx--;
    2651        8244 :         if (nMaxIdx < 0)
    2652             :         {
    2653        3749 :             return false;
    2654             :         }
    2655             :     }
    2656        1157 :     maxIdxOut = nMaxIdx;
    2657             : 
    2658             :     // Find minimum index that is >= nMinVal
    2659        1157 :     nMinIdx = 0;
    2660        8806 :     while (nMaxIdx - nMinIdx >= 2)
    2661             :     {
    2662        7649 :         int nIdx = (nMinIdx + nMaxIdx) / 2;
    2663        7649 :         const GInt64 nVal = GetInt64(pBaseAddr, nIdx);
    2664        7649 :         if (nVal >= nMinVal)
    2665        3013 :             nMaxIdx = nIdx;
    2666             :         else
    2667        4636 :             nMinIdx = nIdx;
    2668             :     }
    2669        2180 :     while (GetInt64(pBaseAddr, nMinIdx) < nMinVal)
    2670             :     {
    2671        1023 :         nMinIdx++;
    2672        1023 :         if (nMinIdx == nVals)
    2673             :         {
    2674           0 :             return false;
    2675             :         }
    2676             :     }
    2677        1157 :     minIdxOut = nMinIdx;
    2678        1157 :     return true;
    2679             : }
    2680             : 
    2681             : /************************************************************************/
    2682             : /*                             FindPages()                              */
    2683             : /************************************************************************/
    2684             : 
    2685        7734 : bool FileGDBSpatialIndexIteratorImpl::FindPages(int iLevel, uint64_t nPage)
    2686             : {
    2687        7734 :     const bool errorRetValue = false;
    2688             : 
    2689        7734 :     iFirstPageIdx[iLevel] = iLastPageIdx[iLevel] = -1;
    2690             : 
    2691             :     const cpl::NonCopyableVector<GByte> *cachedPagePtr =
    2692        7734 :         m_oCachePage[iLevel].getPtr(nPage);
    2693        7734 :     if (cachedPagePtr)
    2694             :     {
    2695        7730 :         memcpy(abyPage[iLevel], cachedPagePtr->data(), m_nPageSize);
    2696             :     }
    2697             :     else
    2698             :     {
    2699           4 :         cpl::NonCopyableVector<GByte> cachedPage;
    2700           4 :         if (m_oCachePage[iLevel].size() == m_oCachePage[iLevel].getMaxSize())
    2701             :         {
    2702           0 :             m_oCachePage[iLevel].removeAndRecycleOldestEntry(cachedPage);
    2703           0 :             cachedPage.clear();
    2704             :         }
    2705             : 
    2706           4 :         VSIFSeekL(fpCurIdx, static_cast<vsi_l_offset>(nPage - 1) * m_nPageSize,
    2707             :                   SEEK_SET);
    2708             : #ifdef DEBUG
    2709           4 :         iLoadedPage[iLevel] = nPage;
    2710             : #endif
    2711           4 :         returnErrorIf(VSIFReadL(abyPage[iLevel], m_nPageSize, 1, fpCurIdx) !=
    2712             :                       1);
    2713           0 :         cachedPage.insert(cachedPage.end(), abyPage[iLevel],
    2714           4 :                           abyPage[iLevel] + m_nPageSize);
    2715           4 :         m_oCachePage[iLevel].insert(nPage, std::move(cachedPage));
    2716             :     }
    2717             : 
    2718        7734 :     nSubPagesCount[iLevel] = GetUInt32(abyPage[iLevel] + m_nObjectIDSize, 0);
    2719        7734 :     returnErrorIf(nSubPagesCount[iLevel] == 0 ||
    2720             :                   nSubPagesCount[iLevel] > nMaxPerPages);
    2721             : 
    2722        7734 :     if (GetInt64(abyPage[iLevel] + m_nOffsetFirstValInPage, 0) > m_nMaxVal)
    2723             :     {
    2724        7700 :         iFirstPageIdx[iLevel] = 0;
    2725             :         // nSubPagesCount[iLevel] == 1 && GetUInt32(abyPage[iLevel] + m_nLeafPageHeaderSize, 0) ==
    2726             :         // 0 should only happen on non-nominal cases where one forces the depth
    2727             :         // of the index to be greater than needed.
    2728        7700 :         if (m_nVersion == 1)
    2729             :         {
    2730        7700 :             iLastPageIdx[iLevel] =
    2731        7700 :                 (nSubPagesCount[iLevel] == 1 &&
    2732        4358 :                  GetUInt32(abyPage[iLevel] + m_nLeafPageHeaderSize, 0) == 0)
    2733       12058 :                     ? 0
    2734             :                     : 1;
    2735             :         }
    2736             :         else
    2737             :         {
    2738           0 :             iLastPageIdx[iLevel] =
    2739           0 :                 (nSubPagesCount[iLevel] == 1 &&
    2740           0 :                  GetUInt64(abyPage[iLevel] + m_nLeafPageHeaderSize, 0) == 0)
    2741           0 :                     ? 0
    2742             :                     : 1;
    2743             :         }
    2744             :     }
    2745          34 :     else if (!FindMinMaxIdx(abyPage[iLevel] + m_nOffsetFirstValInPage,
    2746          34 :                             static_cast<int>(nSubPagesCount[iLevel]), m_nMinVal,
    2747          34 :                             m_nMaxVal, iFirstPageIdx[iLevel],
    2748          34 :                             iLastPageIdx[iLevel]))
    2749             :     {
    2750           0 :         iFirstPageIdx[iLevel] = iLastPageIdx[iLevel] = nSubPagesCount[iLevel];
    2751             :     }
    2752          34 :     else if (iLastPageIdx[iLevel] < static_cast<int>(nSubPagesCount[iLevel]))
    2753             :     {
    2754             :         // Candidate values might extend to the following sub-page
    2755          34 :         iLastPageIdx[iLevel]++;
    2756             :     }
    2757             : 
    2758        7734 :     return true;
    2759             : }
    2760             : 
    2761             : /************************************************************************/
    2762             : /*                              GetNextRow()                            */
    2763             : /************************************************************************/
    2764             : 
    2765       21144 : int FileGDBSpatialIndexIteratorImpl::GetNextRow()
    2766             : {
    2767       21144 :     const int errorRetValue = -1;
    2768       21144 :     if (bEOF)
    2769           0 :         return -1;
    2770             : 
    2771             :     while (true)
    2772             :     {
    2773       24527 :         if (iCurFeatureInPage >= nFeaturesInPage)
    2774             :         {
    2775        5640 :             int nMinIdx = 0;
    2776        5640 :             int nMaxIdx = 0;
    2777        5640 :             if (!LoadNextFeaturePage() ||
    2778        4872 :                 !FindMinMaxIdx(abyPageFeature + m_nOffsetFirstValInPage,
    2779             :                                nFeaturesInPage, m_nMinVal, m_nMaxVal, nMinIdx,
    2780       10512 :                                nMaxIdx) ||
    2781        1123 :                 nMinIdx > nMaxIdx)
    2782             :             {
    2783        4517 :                 if (m_nCurX < m_nMaxX)
    2784             :                 {
    2785        3383 :                     m_nCurX++;
    2786        3383 :                     if (ReadNewXRange())
    2787        3383 :                         continue;
    2788             :                 }
    2789             :                 else
    2790             :                 {
    2791             :                     const auto &gridRes =
    2792        1134 :                         poParent->GetSpatialIndexGridResolution();
    2793        1134 :                     if (m_nGridNo + 1 < static_cast<int>(gridRes.size()) &&
    2794           0 :                         gridRes[m_nGridNo + 1] > 0)
    2795             :                     {
    2796           0 :                         m_nGridNo++;
    2797           0 :                         m_nCurX = static_cast<GInt32>(std::min(
    2798           0 :                             std::max(0.0,
    2799           0 :                                      GetScaledCoord(m_sFilterEnvelope.MinX)),
    2800           0 :                             static_cast<double>(INT_MAX)));
    2801           0 :                         m_nMaxX = static_cast<GInt32>(std::min(
    2802           0 :                             std::max(0.0,
    2803           0 :                                      GetScaledCoord(m_sFilterEnvelope.MaxX)),
    2804           0 :                             static_cast<double>(INT_MAX)));
    2805           0 :                         if (ReadNewXRange())
    2806           0 :                             continue;
    2807             :                     }
    2808             :                 }
    2809             : 
    2810        1134 :                 bEOF = true;
    2811        1134 :                 return -1;
    2812             :             }
    2813             : 
    2814        1123 :             iCurFeatureInPage = nMinIdx;
    2815        1123 :             nFeaturesInPage = nMaxIdx + 1;
    2816             :         }
    2817             : 
    2818             : #ifdef DEBUG
    2819       20010 :         const GInt64 nVal = GetInt64(abyPageFeature + m_nOffsetFirstValInPage,
    2820       20010 :                                      iCurFeatureInPage);
    2821       20010 :         CPL_IGNORE_RET_VAL(nVal);
    2822       20010 :         CPLAssert(nVal >= m_nMinVal && nVal <= m_nMaxVal);
    2823             : #endif
    2824             : 
    2825             :         const GUInt64 nFID =
    2826       20010 :             m_nVersion == 1 ? GetUInt32(abyPageFeature + m_nLeafPageHeaderSize,
    2827             :                                         iCurFeatureInPage)
    2828           7 :                             : GetUInt64(abyPageFeature + m_nLeafPageHeaderSize,
    2829       20010 :                                         iCurFeatureInPage);
    2830       20010 :         iCurFeatureInPage++;
    2831       20010 :         returnErrorAndCleanupIf(
    2832             :             nFID < 1 ||
    2833             :                 nFID > static_cast<GUInt64>(poParent->GetTotalRecordCount()),
    2834             :             bEOF = true);
    2835       20010 :         return static_cast<int>(nFID - 1);
    2836        3383 :     }
    2837             : }
    2838             : 
    2839             : /************************************************************************/
    2840             : /*                             Reset()                                  */
    2841             : /************************************************************************/
    2842             : 
    2843        3860 : bool FileGDBSpatialIndexIteratorImpl::ResetInternal()
    2844             : {
    2845        3860 :     m_nGridNo = 0;
    2846             : 
    2847        3860 :     const auto &gridRes = poParent->GetSpatialIndexGridResolution();
    2848        7720 :     if (gridRes.empty() ||  // shouldn't happen
    2849        3860 :         !(gridRes[0] > 0))
    2850             :     {
    2851           0 :         return false;
    2852             :     }
    2853             : 
    2854        3860 :     m_nCurX = static_cast<GInt32>(
    2855       11580 :         std::min(std::max(0.0, GetScaledCoord(m_sFilterEnvelope.MinX)),
    2856        3860 :                  static_cast<double>(INT_MAX)));
    2857        3860 :     m_nMaxX = static_cast<GInt32>(
    2858       11580 :         std::min(std::max(0.0, GetScaledCoord(m_sFilterEnvelope.MaxX)),
    2859        3860 :                  static_cast<double>(INT_MAX)));
    2860        3860 :     m_nVectorIdx = 0;
    2861        3860 :     return ReadNewXRange();
    2862             : }
    2863             : 
    2864        2683 : void FileGDBSpatialIndexIteratorImpl::Reset()
    2865             : {
    2866        2683 :     ResetInternal();
    2867        2683 : }
    2868             : 
    2869             : /************************************************************************/
    2870             : /*                        GetNextRowSortedByFID()                       */
    2871             : /************************************************************************/
    2872             : 
    2873       10520 : int64_t FileGDBSpatialIndexIteratorImpl::GetNextRowSortedByFID()
    2874             : {
    2875       10520 :     if (m_nVectorIdx == 0)
    2876             :     {
    2877        1254 :         if (!m_bHasBuiltSetFID)
    2878             :         {
    2879        1134 :             m_bHasBuiltSetFID = true;
    2880             :             // Accumulating in a vector and sorting is measurably faster
    2881             :             // than using a unordered_set (or set)
    2882             :             while (true)
    2883             :             {
    2884       21144 :                 const auto nFID = GetNextRow();
    2885       21144 :                 if (nFID < 0)
    2886        1134 :                     break;
    2887       20010 :                 m_oFIDVector.push_back(nFID);
    2888       20010 :             }
    2889        1134 :             std::sort(m_oFIDVector.begin(), m_oFIDVector.end());
    2890             :         }
    2891             : 
    2892        1254 :         if (m_oFIDVector.empty())
    2893         143 :             return -1;
    2894        1111 :         const auto nFID = m_oFIDVector[m_nVectorIdx];
    2895        1111 :         ++m_nVectorIdx;
    2896        1111 :         return nFID;
    2897             :     }
    2898             : 
    2899        9266 :     const auto nLastFID = m_oFIDVector[m_nVectorIdx - 1];
    2900        9783 :     while (m_nVectorIdx < m_oFIDVector.size())
    2901             :     {
    2902             :         // Do not return consecutive identical FID
    2903        9698 :         const auto nFID = m_oFIDVector[m_nVectorIdx];
    2904        9698 :         ++m_nVectorIdx;
    2905        9698 :         if (nFID == nLastFID)
    2906             :         {
    2907         517 :             continue;
    2908             :         }
    2909        9181 :         return nFID;
    2910             :     }
    2911          85 :     return -1;
    2912             : }
    2913             : 
    2914             : } /* namespace OpenFileGDB */

Generated by: LCOV version 1.14