LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/openfilegdb - filegdbindex.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1068 1219 87.6 %
Date: 2025-07-02 23:05:47 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 :     virtual ~FileGDBTrivialIterator()
      82          72 :     {
      83          72 :         delete poParentIter;
      84         144 :     }
      85             : 
      86           3 :     virtual FileGDBTable *GetTable() override
      87             :     {
      88           3 :         return poTable;
      89             :     }
      90             : 
      91         146 :     virtual void Reset() override
      92             :     {
      93         146 :         iRow = 0;
      94         146 :         poParentIter->Reset();
      95         146 :     }
      96             : 
      97             :     virtual int64_t GetNextRowSortedByFID() override;
      98             : 
      99          40 :     virtual int64_t GetRowCount() override
     100             :     {
     101          40 :         return poTable->GetTotalRecordCount();
     102             :     }
     103             : 
     104         680 :     virtual int64_t GetNextRowSortedByValue() override
     105             :     {
     106         680 :         return poParentIter->GetNextRowSortedByValue();
     107             :     }
     108             : 
     109          13 :     virtual const OGRField *GetMinValue(int &eOutType) override
     110             :     {
     111          13 :         return poParentIter->GetMinValue(eOutType);
     112             :     }
     113             : 
     114          34 :     virtual 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             :     virtual ~FileGDBNotIterator();
     144             : 
     145           4 :     virtual FileGDBTable *GetTable() override
     146             :     {
     147           4 :         return poTable;
     148             :     }
     149             : 
     150             :     virtual void Reset() override;
     151             :     virtual int64_t GetNextRowSortedByFID() override;
     152             :     virtual 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             :     virtual ~FileGDBAndIterator();
     174             : 
     175           0 :     virtual FileGDBTable *GetTable() override
     176             :     {
     177           0 :         return poIter1->GetTable();
     178             :     }
     179             : 
     180             :     virtual void Reset() override;
     181             :     virtual 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             :     virtual ~FileGDBOrIterator();
     204             : 
     205           6 :     virtual FileGDBTable *GetTable() override
     206             :     {
     207           6 :         return poIter1->GetTable();
     208             :     }
     209             : 
     210             :     virtual void Reset() override;
     211             :     virtual int64_t GetNextRowSortedByFID() override;
     212             :     virtual 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 : 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             :     virtual ~FileGDBIndexIteratorBase();
     300             : 
     301         156 :     virtual FileGDBTable *GetTable() override
     302             :     {
     303         156 :         return poParent;
     304             :     }
     305             : 
     306             :     virtual 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             :     virtual 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             :     virtual ~FileGDBIndexIterator();
     356             : 
     357             :     static FileGDBIterator *Build(FileGDBTable *poParentIn, int nFieldIdx,
     358             :                                   int bAscendingIn, FileGDBSQLOp op,
     359             :                                   OGRFieldType eOGRFieldType,
     360             :                                   const OGRField *psValue);
     361             : 
     362             :     virtual int64_t GetNextRowSortedByFID() override;
     363             :     virtual int64_t GetRowCount() override;
     364             :     virtual void Reset() override;
     365             : 
     366         734 :     virtual int64_t GetNextRowSortedByValue() override
     367             :     {
     368         734 :         return GetNextRow();
     369             :     }
     370             : 
     371             :     virtual const OGRField *GetMinValue(int &eOutType) override;
     372             :     virtual 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             :     const std::string osAtxName =
    1136        1616 :         CPLFormFilenameSafe(
    1137         808 :             CPLGetPathSafe(poParent->GetFilename().c_str()).c_str(),
    1138         404 :             CPLGetBasenameSafe(poParent->GetFilename().c_str()).c_str(),
    1139         404 :             poIndex->GetIndexName().c_str())
    1140         808 :             .append(".atx");
    1141             : 
    1142         404 :     if (!ReadTrailer(osAtxName.c_str()))
    1143           3 :         return FALSE;
    1144         401 :     returnErrorIf(m_nValueCountInIdx >
    1145             :                   static_cast<GUInt64>(poParent->GetValidRecordCount()));
    1146             : 
    1147         400 :     switch (eFieldType)
    1148             :     {
    1149          10 :         case FGFT_INT16:
    1150          10 :             returnErrorIf(m_nValueSize != sizeof(GUInt16));
    1151          10 :             if (eOp != FGSO_ISNOTNULL)
    1152             :             {
    1153           8 :                 returnErrorIf(eOGRFieldType != OFTInteger);
    1154           8 :                 sValue.Integer = psValue->Integer;
    1155             :             }
    1156          10 :             break;
    1157         118 :         case FGFT_INT32:
    1158         118 :             returnErrorIf(m_nValueSize != sizeof(GUInt32));
    1159         118 :             if (eOp != FGSO_ISNOTNULL)
    1160             :             {
    1161          94 :                 returnErrorIf(eOGRFieldType != OFTInteger);
    1162          94 :                 sValue.Integer = psValue->Integer;
    1163             :             }
    1164         118 :             break;
    1165          20 :         case FGFT_FLOAT32:
    1166          20 :             returnErrorIf(m_nValueSize != sizeof(float));
    1167          20 :             if (eOp != FGSO_ISNOTNULL)
    1168             :             {
    1169          18 :                 returnErrorIf(eOGRFieldType != OFTReal);
    1170          18 :                 sValue.Real = psValue->Real;
    1171             :             }
    1172          20 :             break;
    1173          32 :         case FGFT_FLOAT64:
    1174          32 :             returnErrorIf(m_nValueSize != sizeof(double));
    1175          32 :             if (eOp != FGSO_ISNOTNULL)
    1176             :             {
    1177          20 :                 returnErrorIf(eOGRFieldType != OFTReal);
    1178          20 :                 sValue.Real = psValue->Real;
    1179             :             }
    1180          32 :             break;
    1181         186 :         case FGFT_STRING:
    1182             :         {
    1183         186 :             returnErrorIf((m_nValueSize % 2) != 0);
    1184         186 :             returnErrorIf(m_nValueSize == 0);
    1185         186 :             returnErrorIf(m_nValueSize > 2 * MAX_CAR_COUNT_INDEXED_STR);
    1186         186 :             nStrLen = m_nValueSize / 2;
    1187         186 :             if (eOp != FGSO_ISNOTNULL)
    1188             :             {
    1189         156 :                 returnErrorIf(eOGRFieldType != OFTString);
    1190         156 :                 wchar_t *pWide = CPLRecodeToWChar(psValue->String, CPL_ENC_UTF8,
    1191             :                                                   CPL_ENC_UCS2);
    1192         156 :                 returnErrorIf(pWide == nullptr);
    1193         156 :                 int nCount = 0;
    1194        2497 :                 while (pWide[nCount] != 0)
    1195             :                 {
    1196        2341 :                     returnErrorAndCleanupIf(nCount == nStrLen, CPLFree(pWide));
    1197        2341 :                     asUTF16Str[nCount] = pWide[nCount];
    1198        2341 :                     nCount++;
    1199             :                 }
    1200        1970 :                 while (nCount < nStrLen)
    1201             :                 {
    1202        1814 :                     asUTF16Str[nCount] = 32; /* space character */
    1203        1814 :                     nCount++;
    1204             :                 }
    1205         156 :                 CPLFree(pWide);
    1206             :             }
    1207         186 :             break;
    1208             :         }
    1209             : 
    1210          16 :         case FGFT_DATETIME:
    1211             :         case FGFT_DATE:
    1212             :         case FGFT_DATETIME_WITH_OFFSET:
    1213             :         {
    1214          16 :             returnErrorIf(m_nValueSize != sizeof(double));
    1215          16 :             if (eOp != FGSO_ISNOTNULL)
    1216             :             {
    1217           8 :                 returnErrorIf(
    1218             :                     eOGRFieldType != OFTReal && eOGRFieldType != OFTDateTime &&
    1219             :                     eOGRFieldType != OFTDate && eOGRFieldType != OFTTime);
    1220           8 :                 if (eOGRFieldType == OFTReal)
    1221           0 :                     sValue.Real = psValue->Real;
    1222             :                 else
    1223           8 :                     sValue.Real = FileGDBOGRDateToDoubleDate(
    1224             :                         psValue, true,
    1225           8 :                         /* bHighPrecision= */ eFieldType ==
    1226          16 :                                 FGFT_DATETIME_WITH_OFFSET ||
    1227           8 :                             poField->IsHighPrecision());
    1228             :             }
    1229          16 :             break;
    1230             :         }
    1231             : 
    1232           8 :         case FGFT_GUID:
    1233             :         case FGFT_GLOBALID:
    1234             :         {
    1235           8 :             returnErrorIf(m_nValueSize != UUID_LEN_AS_STRING);
    1236           8 :             if (eOp != FGSO_ISNOTNULL)
    1237             :             {
    1238           7 :                 returnErrorIf(eOGRFieldType != OFTString);
    1239           7 :                 memset(szUUID, 0, UUID_LEN_AS_STRING + 1);
    1240             :                 // cppcheck-suppress redundantCopy
    1241           7 :                 strncpy(szUUID, psValue->String, UUID_LEN_AS_STRING);
    1242           9 :                 bEvaluateToFALSE = eOp == FGSO_EQ &&
    1243           2 :                                    strlen(psValue->String) !=
    1244             :                                        static_cast<size_t>(UUID_LEN_AS_STRING);
    1245             :             }
    1246           8 :             break;
    1247             :         }
    1248             : 
    1249           8 :         case FGFT_INT64:
    1250           8 :             returnErrorIf(m_nValueSize != sizeof(int64_t));
    1251           8 :             if (eOp != FGSO_ISNOTNULL)
    1252             :             {
    1253           4 :                 returnErrorIf(eOGRFieldType != OFTInteger64);
    1254           4 :                 sValue.Integer64 = psValue->Integer64;
    1255             :             }
    1256           8 :             break;
    1257             : 
    1258           2 :         case FGFT_TIME:
    1259             :         {
    1260           2 :             returnErrorIf(m_nValueSize != sizeof(double));
    1261           2 :             if (eOp != FGSO_ISNOTNULL)
    1262             :             {
    1263           0 :                 returnErrorIf(eOGRFieldType != OFTReal &&
    1264             :                               eOGRFieldType != OFTTime);
    1265           0 :                 if (eOGRFieldType == OFTReal)
    1266           0 :                     sValue.Real = psValue->Real;
    1267             :                 else
    1268           0 :                     sValue.Real = FileGDBOGRTimeToDoubleTime(psValue);
    1269             :             }
    1270           2 :             break;
    1271             :         }
    1272             : 
    1273           0 :         default:
    1274           0 :             CPLAssert(false);
    1275             :             break;
    1276             :     }
    1277             : 
    1278         400 :     if (m_nValueCountInIdx > 0)
    1279             :     {
    1280         394 :         if (nIndexDepth == 1)
    1281             :         {
    1282         357 :             iFirstPageIdx[0] = iLastPageIdx[0] = 0;
    1283             :         }
    1284             :         else
    1285             :         {
    1286          37 :             returnErrorIf(!FindPages(0, 1));
    1287             :         }
    1288             :     }
    1289             : 
    1290             :     // To avoid 'spamming' on huge raster files
    1291         399 :     if (poField->GetName() != "block_key")
    1292             :     {
    1293         500 :         CPLDebug("OpenFileGDB", "Using index on field %s (%s %s)",
    1294         250 :                  poField->GetName().c_str(), FileGDBSQLOpToStr(eOp),
    1295             :                  FileGDBValueToStr(eOGRFieldType, psValue));
    1296             :     }
    1297             : 
    1298         399 :     Reset();
    1299             : 
    1300         399 :     return TRUE;
    1301             : }
    1302             : 
    1303             : /************************************************************************/
    1304             : /*                          FileGDBUTF16StrCompare()                    */
    1305             : /************************************************************************/
    1306             : 
    1307        1580 : static int FileGDBUTF16StrCompare(const GUInt16 *pasFirst,
    1308             :                                   const GUInt16 *pasSecond, int nStrLen,
    1309             :                                   bool bCaseInsensitive)
    1310             : {
    1311       18115 :     for (int i = 0; i < nStrLen; i++)
    1312             :     {
    1313       17966 :         GUInt16 chA = pasFirst[i];
    1314       17966 :         GUInt16 chB = pasSecond[i];
    1315       17966 :         if (bCaseInsensitive)
    1316             :         {
    1317         109 :             if (chA >= 'a' && chA <= 'z')
    1318          14 :                 chA -= 'a' - 'A';
    1319         109 :             if (chB >= 'a' && chB <= 'z')
    1320          31 :                 chB -= 'a' - 'A';
    1321             :         }
    1322       17966 :         if (chA < chB)
    1323          19 :             return -1;
    1324       17947 :         if (chA > chB)
    1325        1412 :             return 1;
    1326             :     }
    1327         149 :     return 0;
    1328             : }
    1329             : 
    1330             : /************************************************************************/
    1331             : /*                              COMPARE()                               */
    1332             : /************************************************************************/
    1333             : 
    1334             : #define COMPARE(a, b) (((a) < (b)) ? -1 : ((a) == (b)) ? 0 : 1)
    1335             : 
    1336             : /************************************************************************/
    1337             : /*                             FindPages()                              */
    1338             : /************************************************************************/
    1339             : 
    1340          40 : bool FileGDBIndexIterator::FindPages(int iLevel, uint64_t nPage)
    1341             : {
    1342          40 :     const bool errorRetValue = false;
    1343          40 :     VSIFSeekL(fpCurIdx, static_cast<vsi_l_offset>(nPage - 1) * m_nPageSize,
    1344             :               SEEK_SET);
    1345             : #ifdef DEBUG
    1346          40 :     iLoadedPage[iLevel] = nPage;
    1347             : #endif
    1348          40 :     returnErrorIf(VSIFReadL(abyPage[iLevel], m_nPageSize, 1, fpCurIdx) != 1);
    1349             : 
    1350          40 :     nSubPagesCount[iLevel] = GetUInt32(abyPage[iLevel] + m_nObjectIDSize, 0);
    1351          40 :     returnErrorIf(nSubPagesCount[iLevel] == 0 ||
    1352             :                   nSubPagesCount[iLevel] > nMaxPerPages);
    1353          39 :     if (nIndexDepth == 2)
    1354          34 :         returnErrorIf(m_nValueCountInIdx > static_cast<uint64_t>(nMaxPerPages) *
    1355             :                                                (nSubPagesCount[0] + 1));
    1356             : 
    1357          39 :     if (eOp == FGSO_ISNOTNULL)
    1358             :     {
    1359          15 :         iFirstPageIdx[iLevel] = 0;
    1360          15 :         iLastPageIdx[iLevel] = nSubPagesCount[iLevel];
    1361          15 :         return true;
    1362             :     }
    1363             : 
    1364             :     GUInt32 i;
    1365             : #ifdef DEBUG_INDEX_CONSISTENCY
    1366             :     double dfLastMax = 0.0;
    1367             :     int nLastMax = 0;
    1368             :     GUInt16 asLastMax[MAX_CAR_COUNT_INDEXED_STR] = {0};
    1369             :     char szLastMaxUUID[UUID_LEN_AS_STRING + 1] = {0};
    1370             : #endif
    1371          24 :     iFirstPageIdx[iLevel] = iLastPageIdx[iLevel] = -1;
    1372             : 
    1373          46 :     for (i = 0; i < nSubPagesCount[iLevel]; i++)
    1374             :     {
    1375             :         int nComp;
    1376             : 
    1377          26 :         switch (eFieldType)
    1378             :         {
    1379           0 :             case FGFT_INT16:
    1380             :             {
    1381             :                 GInt16 nVal =
    1382           0 :                     GetInt16(abyPage[iLevel] + m_nOffsetFirstValInPage, i);
    1383             : #ifdef DEBUG_INDEX_CONSISTENCY
    1384             :                 returnErrorIf(i > 0 && nVal < nLastMax);
    1385             :                 nLastMax = nVal;
    1386             : #endif
    1387           0 :                 nComp = COMPARE(sValue.Integer, nVal);
    1388           0 :                 break;
    1389             :             }
    1390             : 
    1391           2 :             case FGFT_INT32:
    1392             :             {
    1393             :                 GInt32 nVal =
    1394           2 :                     GetInt32(abyPage[iLevel] + m_nOffsetFirstValInPage, i);
    1395             : #ifdef DEBUG_INDEX_CONSISTENCY
    1396             :                 returnErrorIf(i > 0 && nVal < nLastMax);
    1397             :                 nLastMax = nVal;
    1398             : #endif
    1399           2 :                 nComp = COMPARE(sValue.Integer, nVal);
    1400           2 :                 break;
    1401             :             }
    1402             : 
    1403           0 :             case FGFT_INT64:
    1404             :             {
    1405             :                 int64_t nVal =
    1406           0 :                     GetInt64(abyPage[iLevel] + m_nOffsetFirstValInPage, i);
    1407             : #ifdef DEBUG_INDEX_CONSISTENCY
    1408             :                 returnErrorIf(i > 0 && nVal < nLastMax);
    1409             :                 nLastMax = nVal;
    1410             : #endif
    1411           0 :                 nComp = COMPARE(sValue.Integer64, nVal);
    1412           0 :                 break;
    1413             :             }
    1414             : 
    1415           0 :             case FGFT_FLOAT32:
    1416             :             {
    1417             :                 float fVal =
    1418           0 :                     GetFloat32(abyPage[iLevel] + m_nOffsetFirstValInPage, i);
    1419             : #ifdef DEBUG_INDEX_CONSISTENCY
    1420             :                 returnErrorIf(i > 0 && fVal < dfLastMax);
    1421             :                 dfLastMax = fVal;
    1422             : #endif
    1423           0 :                 nComp = COMPARE(sValue.Real, fVal);
    1424           0 :                 break;
    1425             :             }
    1426             : 
    1427          13 :             case FGFT_FLOAT64:
    1428             :             {
    1429             :                 const double dfVal =
    1430          13 :                     GetFloat64(abyPage[iLevel] + m_nOffsetFirstValInPage, i);
    1431             : #ifdef DEBUG_INDEX_CONSISTENCY
    1432             :                 returnErrorIf(i > 0 && dfVal < dfLastMax);
    1433             :                 dfLastMax = dfVal;
    1434             : #endif
    1435          13 :                 nComp = COMPARE(sValue.Real, dfVal);
    1436          13 :                 break;
    1437             :             }
    1438             : 
    1439           0 :             case FGFT_DATETIME:
    1440             :             case FGFT_DATE:
    1441             :             case FGFT_TIME:
    1442             :             {
    1443             :                 const double dfVal =
    1444           0 :                     GetFloat64(abyPage[iLevel] + m_nOffsetFirstValInPage, i);
    1445             : #ifdef DEBUG_INDEX_CONSISTENCY
    1446             :                 returnErrorIf(i > 0 && dfVal < dfLastMax);
    1447             :                 dfLastMax = dfVal;
    1448             : #endif
    1449           0 :                 if (sValue.Real + 1e-10 < dfVal)
    1450           0 :                     nComp = -1;
    1451           0 :                 else if (sValue.Real - 1e-10 > dfVal)
    1452           0 :                     nComp = 1;
    1453             :                 else
    1454           0 :                     nComp = 0;
    1455           0 :                 break;
    1456             :             }
    1457             : 
    1458          11 :             case FGFT_STRING:
    1459             :             {
    1460             :                 GUInt16 *pasMax;
    1461             :                 GUInt16 asMax[MAX_CAR_COUNT_INDEXED_STR];
    1462          11 :                 pasMax = asMax;
    1463          11 :                 memcpy(asMax,
    1464          11 :                        abyPage[iLevel] + m_nOffsetFirstValInPage +
    1465          11 :                            nStrLen * sizeof(GUInt16) * i,
    1466          11 :                        nStrLen * sizeof(GUInt16));
    1467         717 :                 for (int j = 0; j < nStrLen; j++)
    1468         706 :                     CPL_LSBPTR16(&asMax[j]);
    1469             :                     // Note: we have an inconsistency. OGR SQL equality operator
    1470             :                     // is advertized to be case insensitive, but we have always
    1471             :                     // implemented FGSO_EQ as case sensitive.
    1472             : #ifdef DEBUG_INDEX_CONSISTENCY
    1473             :                 returnErrorIf(i > 0 &&
    1474             :                               FileGDBUTF16StrCompare(pasMax, asLastMax, nStrLen,
    1475             :                                                      eOp == FGSO_ILIKE) < 0);
    1476             :                 memcpy(asLastMax, pasMax, nStrLen * 2);
    1477             : #endif
    1478          11 :                 nComp = FileGDBUTF16StrCompare(asUTF16Str, pasMax, nStrLen,
    1479          11 :                                                eOp == FGSO_ILIKE);
    1480          11 :                 break;
    1481             :             }
    1482             : 
    1483           0 :             case FGFT_GUID:
    1484             :             case FGFT_GLOBALID:
    1485             :             {
    1486           0 :                 const char *psNonzMaxUUID = reinterpret_cast<char *>(
    1487           0 :                     abyPage[iLevel] + m_nOffsetFirstValInPage +
    1488           0 :                     UUID_LEN_AS_STRING * i);
    1489             : #ifdef DEBUG_INDEX_CONSISTENCY
    1490             :                 returnErrorIf(i > 0 && memcmp(psNonzMaxUUID, szLastMaxUUID,
    1491             :                                               UUID_LEN_AS_STRING) < 0);
    1492             :                 memcpy(szLastMaxUUID, psNonzMaxUUID, UUID_LEN_AS_STRING);
    1493             : #endif
    1494           0 :                 nComp = memcmp(szUUID, psNonzMaxUUID, UUID_LEN_AS_STRING);
    1495           0 :                 break;
    1496             :             }
    1497             : 
    1498           0 :             default:
    1499           0 :                 CPLAssert(false);
    1500             :                 nComp = 0;
    1501             :                 break;
    1502             :         }
    1503             : 
    1504          26 :         int bStop = FALSE;
    1505          26 :         switch (eOp)
    1506             :         {
    1507             :             /* dfVal = 1 2 2 3 3 4 */
    1508             :             /* sValue.Real = 3 */
    1509             :             /* nComp = (sValue.Real < dfVal) ? -1 : (sValue.Real == dfVal) ? 0 :
    1510             :              * 1; */
    1511           3 :             case FGSO_LT:
    1512             :             case FGSO_LE:
    1513           3 :                 if (iFirstPageIdx[iLevel] < 0)
    1514             :                 {
    1515           3 :                     iFirstPageIdx[iLevel] = iLastPageIdx[iLevel] =
    1516           3 :                         static_cast<int>(i);
    1517             :                 }
    1518             :                 else
    1519             :                 {
    1520           0 :                     iLastPageIdx[iLevel] = static_cast<int>(i);
    1521           0 :                     if (nComp < 0)
    1522             :                     {
    1523           0 :                         bStop = TRUE;
    1524             :                     }
    1525             :                 }
    1526           3 :                 break;
    1527             : 
    1528          21 :             case FGSO_EQ:
    1529             :             case FGSO_ILIKE:
    1530          21 :                 if (iFirstPageIdx[iLevel] < 0)
    1531             :                 {
    1532          19 :                     if (nComp <= 0)
    1533          11 :                         iFirstPageIdx[iLevel] = iLastPageIdx[iLevel] =
    1534          11 :                             static_cast<int>(i);
    1535             :                 }
    1536             :                 else
    1537             :                 {
    1538           2 :                     if (nComp == 0)
    1539           0 :                         iLastPageIdx[iLevel] = static_cast<int>(i);
    1540             :                     else
    1541           2 :                         bStop = TRUE;
    1542             :                 }
    1543          21 :                 break;
    1544             : 
    1545           1 :             case FGSO_GE:
    1546           1 :                 if (iFirstPageIdx[iLevel] < 0)
    1547             :                 {
    1548           1 :                     if (nComp <= 0)
    1549             :                     {
    1550           1 :                         iFirstPageIdx[iLevel] = static_cast<int>(i);
    1551           1 :                         iLastPageIdx[iLevel] = nSubPagesCount[iLevel];
    1552           1 :                         bStop = TRUE;
    1553             :                     }
    1554             :                 }
    1555           1 :                 break;
    1556             : 
    1557           1 :             case FGSO_GT:
    1558           1 :                 if (iFirstPageIdx[iLevel] < 0)
    1559             :                 {
    1560           1 :                     if (nComp < 0)
    1561             :                     {
    1562           1 :                         iFirstPageIdx[iLevel] = static_cast<int>(i);
    1563           1 :                         iLastPageIdx[iLevel] = nSubPagesCount[iLevel];
    1564           1 :                         bStop = TRUE;
    1565             :                     }
    1566             :                 }
    1567           1 :                 break;
    1568             : 
    1569           0 :             case FGSO_ISNOTNULL:
    1570           0 :                 CPLAssert(false);
    1571             :                 break;
    1572             :         }
    1573          26 :         if (bStop)
    1574           4 :             break;
    1575             :     }
    1576             : 
    1577          24 :     if (iFirstPageIdx[iLevel] < 0)
    1578             :     {
    1579           8 :         iFirstPageIdx[iLevel] = iLastPageIdx[iLevel] = nSubPagesCount[iLevel];
    1580             :     }
    1581          16 :     else if (iLastPageIdx[iLevel] < static_cast<int>(nSubPagesCount[iLevel]))
    1582             :     {
    1583          14 :         iLastPageIdx[iLevel]++;
    1584             :     }
    1585             : 
    1586          24 :     return true;
    1587             : }
    1588             : 
    1589             : /************************************************************************/
    1590             : /*                             Reset()                                  */
    1591             : /************************************************************************/
    1592             : 
    1593        8513 : void FileGDBIndexIteratorBase::Reset()
    1594             : {
    1595        8513 :     iCurPageIdx[0] = (bAscending) ? iFirstPageIdx[0] - 1 : iLastPageIdx[0] + 1;
    1596        8513 :     memset(iFirstPageIdx + 1, 0xFF, (MAX_DEPTH - 1) * sizeof(iFirstPageIdx[0]));
    1597        8513 :     memset(iLastPageIdx + 1, 0xFF, (MAX_DEPTH - 1) * sizeof(iLastPageIdx[0]));
    1598        8513 :     memset(iCurPageIdx + 1, 0xFF, (MAX_DEPTH - 1) * sizeof(iCurPageIdx[0]));
    1599        8513 :     memset(nLastPageAccessed, 0, MAX_DEPTH * sizeof(nLastPageAccessed[0]));
    1600        8513 :     iCurFeatureInPage = 0;
    1601        8513 :     nFeaturesInPage = 0;
    1602             : 
    1603        8513 :     bEOF = (m_nValueCountInIdx == 0);
    1604        8513 : }
    1605             : 
    1606             : /************************************************************************/
    1607             : /*                             Reset()                                  */
    1608             : /************************************************************************/
    1609             : 
    1610        1270 : void FileGDBIndexIterator::Reset()
    1611             : {
    1612        1270 :     FileGDBIndexIteratorBase::Reset();
    1613        1270 :     iSorted = 0;
    1614        1270 :     bEOF = bEOF || bEvaluateToFALSE;
    1615        1270 : }
    1616             : 
    1617             : /************************************************************************/
    1618             : /*                           ReadPageNumber()                           */
    1619             : /************************************************************************/
    1620             : 
    1621        7596 : uint64_t FileGDBIndexIteratorBase::ReadPageNumber(int iLevel)
    1622             : {
    1623        7596 :     const int errorRetValue = 0;
    1624             :     uint64_t nPage;
    1625        7596 :     if (m_nVersion == 1)
    1626             :     {
    1627        7596 :         nPage = GetUInt32(abyPage[iLevel] + m_nNonLeafPageHeaderSize,
    1628             :                           iCurPageIdx[iLevel]);
    1629        7596 :         if (nPage == nLastPageAccessed[iLevel])
    1630             :         {
    1631           1 :             if (!LoadNextPage(iLevel))
    1632           0 :                 return 0;
    1633           1 :             nPage = GetUInt32(abyPage[iLevel] + m_nNonLeafPageHeaderSize,
    1634             :                               iCurPageIdx[iLevel]);
    1635             :         }
    1636             :     }
    1637             :     else
    1638             :     {
    1639           0 :         nPage = GetUInt64(abyPage[iLevel] + m_nNonLeafPageHeaderSize,
    1640             :                           iCurPageIdx[iLevel]);
    1641           0 :         if (nPage == nLastPageAccessed[iLevel])
    1642             :         {
    1643           0 :             if (!LoadNextPage(iLevel))
    1644           0 :                 return 0;
    1645           0 :             nPage = GetUInt64(abyPage[iLevel] + m_nNonLeafPageHeaderSize,
    1646             :                               iCurPageIdx[iLevel]);
    1647             :         }
    1648             :     }
    1649        7596 :     nLastPageAccessed[iLevel] = nPage;
    1650        7596 :     returnErrorIf(nPage < 2);
    1651        7596 :     return nPage;
    1652             : }
    1653             : 
    1654             : /************************************************************************/
    1655             : /*                           LoadNextPage()                             */
    1656             : /************************************************************************/
    1657             : 
    1658        7628 : bool FileGDBIndexIteratorBase::LoadNextPage(int iLevel)
    1659             : {
    1660        7628 :     const bool errorRetValue = false;
    1661        7628 :     if ((bAscending && iCurPageIdx[iLevel] == iLastPageIdx[iLevel]) ||
    1662        4244 :         (!bAscending && iCurPageIdx[iLevel] == iFirstPageIdx[iLevel]))
    1663             :     {
    1664        3384 :         if (iLevel == 0 || !LoadNextPage(iLevel - 1))
    1665          31 :             return false;
    1666             : 
    1667        3353 :         const auto nPage = ReadPageNumber(iLevel - 1);
    1668        3353 :         returnErrorIf(!FindPages(iLevel, nPage));
    1669             : 
    1670        3353 :         iCurPageIdx[iLevel] =
    1671        3353 :             (bAscending) ? iFirstPageIdx[iLevel] : iLastPageIdx[iLevel];
    1672             :     }
    1673             :     else
    1674             :     {
    1675        4244 :         if (bAscending)
    1676        4243 :             iCurPageIdx[iLevel]++;
    1677             :         else
    1678           1 :             iCurPageIdx[iLevel]--;
    1679             :     }
    1680             : 
    1681        7597 :     return true;
    1682             : }
    1683             : 
    1684             : /************************************************************************/
    1685             : /*                        LoadNextFeaturePage()                         */
    1686             : /************************************************************************/
    1687             : 
    1688        6807 : bool FileGDBIndexIteratorBase::LoadNextFeaturePage()
    1689             : {
    1690        6807 :     const bool errorRetValue = false;
    1691             :     GUInt64 nPage;
    1692             : 
    1693        6807 :     if (nIndexDepth == 1)
    1694             :     {
    1695        2534 :         if (iCurPageIdx[0] == iLastPageIdx[0])
    1696             :         {
    1697        1020 :             return false;
    1698             :         }
    1699        1514 :         if (bAscending)
    1700        1480 :             iCurPageIdx[0]++;
    1701             :         else
    1702          34 :             iCurPageIdx[0]--;
    1703        1514 :         nPage = 1;
    1704             :     }
    1705             :     else
    1706             :     {
    1707        4273 :         if (!LoadNextPage(nIndexDepth - 2))
    1708             :         {
    1709          30 :             return false;
    1710             :         }
    1711        4243 :         nPage = ReadPageNumber(nIndexDepth - 2);
    1712        4243 :         returnErrorIf(nPage < 2);
    1713             :     }
    1714             : 
    1715             :     const cpl::NonCopyableVector<GByte> *cachedPagePtr =
    1716        5757 :         m_oCacheFeaturePage.getPtr(nPage);
    1717        5757 :     if (cachedPagePtr)
    1718             :     {
    1719        5109 :         memcpy(abyPageFeature, cachedPagePtr->data(), m_nPageSize);
    1720             :     }
    1721             :     else
    1722             :     {
    1723         648 :         cpl::NonCopyableVector<GByte> cachedPage;
    1724         648 :         if (m_oCacheFeaturePage.size() == m_oCacheFeaturePage.getMaxSize())
    1725             :         {
    1726         133 :             m_oCacheFeaturePage.removeAndRecycleOldestEntry(cachedPage);
    1727         133 :             cachedPage.clear();
    1728             :         }
    1729             : 
    1730         648 :         VSIFSeekL(fpCurIdx, static_cast<vsi_l_offset>(nPage - 1) * m_nPageSize,
    1731             :                   SEEK_SET);
    1732             : #ifdef DEBUG
    1733         648 :         iLoadedPage[nIndexDepth - 1] = nPage;
    1734             : #endif
    1735         648 :         returnErrorIf(VSIFReadL(abyPageFeature, m_nPageSize, 1, fpCurIdx) != 1);
    1736           0 :         cachedPage.insert(cachedPage.end(), abyPageFeature,
    1737         647 :                           abyPageFeature + m_nPageSize);
    1738         647 :         m_oCacheFeaturePage.insert(nPage, std::move(cachedPage));
    1739             :     }
    1740             : 
    1741        5756 :     const GUInt32 nFeatures = GetUInt32(abyPageFeature + m_nObjectIDSize, 0);
    1742        5756 :     returnErrorIf(nFeatures > nMaxPerPages);
    1743             : 
    1744        5755 :     nFeaturesInPage = static_cast<int>(nFeatures);
    1745        5755 :     iCurFeatureInPage = (bAscending) ? 0 : nFeaturesInPage - 1;
    1746        5755 :     return nFeatures != 0;
    1747             : }
    1748             : 
    1749             : /************************************************************************/
    1750             : /*                              GetNextRow()                            */
    1751             : /************************************************************************/
    1752             : 
    1753       16856 : int64_t FileGDBIndexIterator::GetNextRow()
    1754             : {
    1755       16856 :     const int64_t errorRetValue = -1;
    1756       16856 :     if (bEOF)
    1757          32 :         return -1;
    1758             : 
    1759             :     while (true)
    1760             :     {
    1761       19619 :         if (iCurFeatureInPage >= nFeaturesInPage || iCurFeatureInPage < 0)
    1762             :         {
    1763        1044 :             if (!LoadNextFeaturePage())
    1764             :             {
    1765         277 :                 bEOF = true;
    1766       16824 :                 return -1;
    1767             :             }
    1768             :         }
    1769             : 
    1770       19342 :         bool bMatch = false;
    1771       19342 :         if (eOp == FGSO_ISNOTNULL)
    1772             :         {
    1773       12831 :             bMatch = true;
    1774             :         }
    1775             :         else
    1776             :         {
    1777        6511 :             int nComp = 0;
    1778        6511 :             switch (eFieldType)
    1779             :             {
    1780          45 :                 case FGFT_INT16:
    1781             :                 {
    1782             :                     const GInt16 nVal =
    1783          45 :                         GetInt16(abyPageFeature + m_nOffsetFirstValInPage,
    1784             :                                  iCurFeatureInPage);
    1785          45 :                     nComp = COMPARE(sValue.Integer, nVal);
    1786          45 :                     break;
    1787             :                 }
    1788             : 
    1789         527 :                 case FGFT_INT32:
    1790             :                 {
    1791             :                     const GInt32 nVal =
    1792         527 :                         GetInt32(abyPageFeature + m_nOffsetFirstValInPage,
    1793             :                                  iCurFeatureInPage);
    1794         527 :                     nComp = COMPARE(sValue.Integer, nVal);
    1795         527 :                     break;
    1796             :                 }
    1797             : 
    1798          95 :                 case FGFT_FLOAT32:
    1799             :                 {
    1800             :                     const float fVal =
    1801          95 :                         GetFloat32(abyPageFeature + m_nOffsetFirstValInPage,
    1802             :                                    iCurFeatureInPage);
    1803          95 :                     nComp = COMPARE(sValue.Real, fVal);
    1804          95 :                     break;
    1805             :                 }
    1806             : 
    1807        4160 :                 case FGFT_FLOAT64:
    1808             :                 {
    1809             :                     const double dfVal =
    1810        4160 :                         GetFloat64(abyPageFeature + m_nOffsetFirstValInPage,
    1811             :                                    iCurFeatureInPage);
    1812        4160 :                     nComp = COMPARE(sValue.Real, dfVal);
    1813        4160 :                     break;
    1814             :                 }
    1815             : 
    1816          55 :                 case FGFT_DATETIME:
    1817             :                 case FGFT_DATE:
    1818             :                 case FGFT_TIME:
    1819             :                 case FGFT_DATETIME_WITH_OFFSET:
    1820             :                 {
    1821             :                     const double dfVal =
    1822          55 :                         GetFloat64(abyPageFeature + m_nOffsetFirstValInPage,
    1823             :                                    iCurFeatureInPage);
    1824          55 :                     if (sValue.Real + 1e-10 < dfVal)
    1825           0 :                         nComp = -1;
    1826          55 :                     else if (sValue.Real - 1e-10 > dfVal)
    1827          10 :                         nComp = 1;
    1828             :                     else
    1829          45 :                         nComp = 0;
    1830          55 :                     break;
    1831             :                 }
    1832             : 
    1833        1569 :                 case FGFT_STRING:
    1834             :                 {
    1835             :                     GUInt16 asVal[MAX_CAR_COUNT_INDEXED_STR];
    1836        1569 :                     memcpy(asVal,
    1837        1569 :                            abyPageFeature + m_nOffsetFirstValInPage +
    1838        1569 :                                nStrLen * 2 * iCurFeatureInPage,
    1839        1569 :                            nStrLen * 2);
    1840       38786 :                     for (int j = 0; j < nStrLen; j++)
    1841       37217 :                         CPL_LSBPTR16(&asVal[j]);
    1842             :                     // Note: we have an inconsistency. OGR SQL equality operator
    1843             :                     // is advertized to be case insensitive, but we have always
    1844             :                     // implemented FGSO_EQ as case sensitive.
    1845        1569 :                     nComp = FileGDBUTF16StrCompare(asUTF16Str, asVal, nStrLen,
    1846        1569 :                                                    eOp == FGSO_ILIKE);
    1847        1569 :                     break;
    1848             :                 }
    1849             : 
    1850          52 :                 case FGFT_GUID:
    1851             :                 case FGFT_GLOBALID:
    1852             :                 {
    1853          52 :                     nComp = memcmp(szUUID,
    1854          52 :                                    abyPageFeature + m_nOffsetFirstValInPage +
    1855          52 :                                        UUID_LEN_AS_STRING * iCurFeatureInPage,
    1856             :                                    UUID_LEN_AS_STRING);
    1857          52 :                     break;
    1858             :                 }
    1859             : 
    1860           8 :                 case FGFT_INT64:
    1861             :                 {
    1862             :                     const int64_t nVal =
    1863           8 :                         GetInt64(abyPageFeature + m_nOffsetFirstValInPage,
    1864             :                                  iCurFeatureInPage);
    1865           8 :                     nComp = COMPARE(sValue.Integer64, nVal);
    1866           8 :                     break;
    1867             :                 }
    1868             : 
    1869           0 :                 default:
    1870           0 :                     CPLAssert(false);
    1871             :                     nComp = 0;
    1872             :                     break;
    1873             :             }
    1874             : 
    1875        6511 :             bMatch = false;
    1876        6511 :             CPL_IGNORE_RET_VAL(bMatch);
    1877        6511 :             switch (eOp)
    1878             :             {
    1879         909 :                 case FGSO_LT:
    1880         909 :                     if (nComp <= 0 && bAscending)
    1881             :                     {
    1882          34 :                         bEOF = true;
    1883          34 :                         return -1;
    1884             :                     }
    1885         875 :                     bMatch = true;
    1886         875 :                     break;
    1887             : 
    1888         126 :                 case FGSO_LE:
    1889         126 :                     if (nComp < 0 && bAscending)
    1890             :                     {
    1891          12 :                         bEOF = true;
    1892          12 :                         return -1;
    1893             :                     }
    1894         114 :                     bMatch = true;
    1895         114 :                     break;
    1896             : 
    1897        4133 :                 case FGSO_EQ:
    1898             :                 case FGSO_ILIKE:
    1899        4133 :                     if (nComp < 0 && bAscending)
    1900             :                     {
    1901         136 :                         bEOF = true;
    1902         136 :                         return -1;
    1903             :                     }
    1904        3997 :                     bMatch = nComp == 0;
    1905        3997 :                     break;
    1906             : 
    1907         848 :                 case FGSO_GE:
    1908         848 :                     bMatch = nComp <= 0;
    1909         848 :                     break;
    1910             : 
    1911         495 :                 case FGSO_GT:
    1912         495 :                     bMatch = nComp < 0;
    1913         495 :                     break;
    1914             : 
    1915           0 :                 case FGSO_ISNOTNULL:
    1916           0 :                     CPLAssert(false);
    1917             :                     break;
    1918             :             }
    1919             :         }
    1920             : 
    1921       19160 :         if (bMatch)
    1922             :         {
    1923             :             const GUInt64 nFID =
    1924       16365 :                 m_nVersion == 1
    1925       16365 :                     ? GetUInt32(abyPageFeature + m_nLeafPageHeaderSize,
    1926             :                                 iCurFeatureInPage)
    1927           0 :                     : GetUInt64(abyPageFeature + m_nLeafPageHeaderSize,
    1928       16365 :                                 iCurFeatureInPage);
    1929       16365 :             if (bAscending)
    1930       16224 :                 iCurFeatureInPage++;
    1931             :             else
    1932         141 :                 iCurFeatureInPage--;
    1933       16365 :             returnErrorAndCleanupIf(
    1934             :                 nFID < 1 || nFID > static_cast<GUInt64>(
    1935             :                                        poParent->GetTotalRecordCount()),
    1936             :                 bEOF = true);
    1937       16365 :             return static_cast<int64_t>(nFID - 1);
    1938             :         }
    1939             :         else
    1940             :         {
    1941        2795 :             if (bAscending)
    1942        2795 :                 iCurFeatureInPage++;
    1943             :             else
    1944           0 :                 iCurFeatureInPage--;
    1945             :         }
    1946        2795 :     }
    1947             : }
    1948             : 
    1949             : /************************************************************************/
    1950             : /*                             SortRows()                               */
    1951             : /************************************************************************/
    1952             : 
    1953          88 : int FileGDBIndexIterator::SortRows()
    1954             : {
    1955          88 :     nSortedCount = 0;
    1956          88 :     iSorted = 0;
    1957          88 :     int nSortedAlloc = 0;
    1958          88 :     Reset();
    1959             :     while (true)
    1960             :     {
    1961        1326 :         int64_t nRow = GetNextRow();
    1962        1326 :         if (nRow < 0)
    1963          88 :             break;
    1964        1238 :         if (nSortedCount == nSortedAlloc)
    1965             :         {
    1966          87 :             int nNewSortedAlloc = 4 * nSortedAlloc / 3 + 16;
    1967             :             int64_t *panNewSortedRows =
    1968          87 :                 static_cast<int64_t *>(VSI_REALLOC_VERBOSE(
    1969             :                     panSortedRows, sizeof(int64_t) * nNewSortedAlloc));
    1970          87 :             if (panNewSortedRows == nullptr)
    1971             :             {
    1972           0 :                 nSortedCount = 0;
    1973           0 :                 return FALSE;
    1974             :             }
    1975          87 :             nSortedAlloc = nNewSortedAlloc;
    1976          87 :             panSortedRows = panNewSortedRows;
    1977             :         }
    1978        1238 :         panSortedRows[nSortedCount++] = nRow;
    1979        1238 :     }
    1980          88 :     if (nSortedCount == 0)
    1981          25 :         return FALSE;
    1982          63 :     std::sort(panSortedRows, panSortedRows + nSortedCount);
    1983             : #ifdef m_nValueCountInIdx_reliable
    1984             :     if (eOp == FGSO_ISNOTNULL && (int64_t)m_nValueCountInIdx != nSortedCount)
    1985             :         PrintError();
    1986             : #endif
    1987          63 :     return TRUE;
    1988             : }
    1989             : 
    1990             : /************************************************************************/
    1991             : /*                        GetNextRowSortedByFID()                       */
    1992             : /************************************************************************/
    1993             : 
    1994        2807 : int64_t FileGDBIndexIterator::GetNextRowSortedByFID()
    1995             : {
    1996        2807 :     if (eOp == FGSO_EQ)
    1997        1040 :         return GetNextRow();
    1998             : 
    1999        1767 :     if (iSorted < nSortedCount)
    2000        1586 :         return panSortedRows[iSorted++];
    2001             : 
    2002         181 :     if (nSortedCount < 0)
    2003             :     {
    2004          88 :         if (!SortRows())
    2005          25 :             return -1;
    2006          63 :         return panSortedRows[iSorted++];
    2007             :     }
    2008             :     else
    2009             :     {
    2010          93 :         return -1;
    2011             :     }
    2012             : }
    2013             : 
    2014             : /************************************************************************/
    2015             : /*                           GetRowCount()                              */
    2016             : /************************************************************************/
    2017             : 
    2018         208 : int64_t FileGDBIndexIterator::GetRowCount()
    2019             : {
    2020             :     // The m_nValueCountInIdx value has been found to be unreliable when the index
    2021             :     // is built as features are inserted (and when they are not in increasing
    2022             :     // order) (with FileGDB SDK 1.3) So disable this optimization as there's no
    2023             :     // fast way to know if the value is reliable or not.
    2024             : #ifdef m_nValueCountInIdx_reliable
    2025             :     if (eOp == FGSO_ISNOTNULL)
    2026             :         return (int64_t)m_nValueCountInIdx;
    2027             : #endif
    2028             : 
    2029         208 :     if (nSortedCount >= 0)
    2030           0 :         return nSortedCount;
    2031             : 
    2032         208 :     int64_t nRowCount = 0;
    2033         208 :     bool bSaveAscending = bAscending;
    2034         208 :     bAscending = true; /* for a tiny bit of more efficiency */
    2035         208 :     Reset();
    2036       13756 :     while (GetNextRow() >= 0)
    2037       13548 :         nRowCount++;
    2038         208 :     bAscending = bSaveAscending;
    2039         208 :     Reset();
    2040         208 :     return nRowCount;
    2041             : }
    2042             : 
    2043             : /************************************************************************/
    2044             : /*                            GetMinMaxValue()                          */
    2045             : /************************************************************************/
    2046             : 
    2047          50 : const OGRField *FileGDBIndexIterator::GetMinMaxValue(OGRField *psField,
    2048             :                                                      int &eOutType, int bIsMin)
    2049             : {
    2050          50 :     const OGRField *errorRetValue = nullptr;
    2051          50 :     eOutType = -1;
    2052          50 :     if (m_nValueCountInIdx == 0)
    2053           2 :         return nullptr;
    2054             : 
    2055          96 :     std::vector<GByte> l_abyPageV;
    2056             :     try
    2057             :     {
    2058          48 :         l_abyPageV.resize(m_nPageSize);
    2059             :     }
    2060           0 :     catch (const std::exception &)
    2061             :     {
    2062           0 :         return nullptr;
    2063             :     }
    2064          48 :     GByte *l_abyPage = l_abyPageV.data();
    2065          48 :     uint64_t nPage = 1;
    2066          52 :     for (GUInt32 iLevel = 0; iLevel < nIndexDepth - 1; iLevel++)
    2067             :     {
    2068           4 :         VSIFSeekL(fpCurIdx, static_cast<vsi_l_offset>(nPage - 1) * m_nPageSize,
    2069             :                   SEEK_SET);
    2070             : #ifdef DEBUG
    2071           4 :         iLoadedPage[iLevel] = nPage;
    2072             : #endif
    2073           4 :         returnErrorIf(VSIFReadL(l_abyPage, m_nPageSize, 1, fpCurIdx) != 1);
    2074           4 :         GUInt32 l_nSubPagesCount = GetUInt32(l_abyPage + m_nObjectIDSize, 0);
    2075           4 :         returnErrorIf(l_nSubPagesCount == 0 || l_nSubPagesCount > nMaxPerPages);
    2076             : 
    2077           4 :         if (m_nVersion == 1)
    2078             :         {
    2079           4 :             nPage = GetUInt32(l_abyPage + m_nNonLeafPageHeaderSize,
    2080             :                               bIsMin ? 0 : l_nSubPagesCount);
    2081             :         }
    2082             :         else
    2083             :         {
    2084           0 :             nPage = GetUInt64(l_abyPage + m_nNonLeafPageHeaderSize,
    2085             :                               bIsMin ? 0 : l_nSubPagesCount);
    2086             :         }
    2087           4 :         returnErrorIf(nPage < 2);
    2088             :     }
    2089             : 
    2090          48 :     VSIFSeekL(fpCurIdx, static_cast<vsi_l_offset>(nPage - 1) * m_nPageSize,
    2091             :               SEEK_SET);
    2092             : #ifdef DEBUG
    2093          48 :     iLoadedPage[nIndexDepth - 1] = nPage;
    2094             : #endif
    2095          48 :     returnErrorIf(VSIFReadL(l_abyPage, m_nPageSize, 1, fpCurIdx) != 1);
    2096             : 
    2097          48 :     GUInt32 nFeatures = GetUInt32(l_abyPage + m_nObjectIDSize, 0);
    2098          48 :     returnErrorIf(nFeatures < 1 || nFeatures > nMaxPerPages);
    2099             : 
    2100          48 :     int iFeature = (bIsMin) ? 0 : nFeatures - 1;
    2101             : 
    2102          48 :     switch (eFieldType)
    2103             :     {
    2104           1 :         case FGFT_INT16:
    2105             :         {
    2106             :             const GInt16 nVal =
    2107           1 :                 GetInt16(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2108           1 :             psField->Integer = nVal;
    2109           1 :             eOutType = OFTInteger;
    2110           1 :             return psField;
    2111             :         }
    2112             : 
    2113           2 :         case FGFT_INT32:
    2114             :         {
    2115             :             const GInt32 nVal =
    2116           2 :                 GetInt32(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2117           2 :             psField->Integer = nVal;
    2118           2 :             eOutType = OFTInteger;
    2119           2 :             return psField;
    2120             :         }
    2121             : 
    2122           1 :         case FGFT_FLOAT32:
    2123             :         {
    2124             :             const float fVal =
    2125           1 :                 GetFloat32(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2126           1 :             psField->Real = fVal;
    2127           1 :             eOutType = OFTReal;
    2128           1 :             return psField;
    2129             :         }
    2130             : 
    2131           1 :         case FGFT_FLOAT64:
    2132             :         {
    2133             :             const double dfVal =
    2134           1 :                 GetFloat64(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2135           1 :             psField->Real = dfVal;
    2136           1 :             eOutType = OFTReal;
    2137           1 :             return psField;
    2138             :         }
    2139             : 
    2140           5 :         case FGFT_DATETIME:
    2141             :         case FGFT_DATETIME_WITH_OFFSET:
    2142             :         {
    2143             :             const double dfVal =
    2144           5 :                 GetFloat64(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2145           5 :             FileGDBDoubleDateToOGRDate(dfVal, false, psField);
    2146           5 :             eOutType = OFTDateTime;
    2147           5 :             return psField;
    2148             :         }
    2149             : 
    2150           2 :         case FGFT_DATE:
    2151             :         {
    2152             :             const double dfVal =
    2153           2 :                 GetFloat64(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2154           2 :             FileGDBDoubleDateToOGRDate(dfVal, false, psField);
    2155           2 :             eOutType = OFTDate;
    2156           2 :             return psField;
    2157             :         }
    2158             : 
    2159           2 :         case FGFT_TIME:
    2160             :         {
    2161             :             const double dfVal =
    2162           2 :                 GetFloat64(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2163           2 :             FileGDBDoubleTimeToOGRTime(dfVal, psField);
    2164           2 :             eOutType = OFTTime;
    2165           2 :             return psField;
    2166             :         }
    2167             : 
    2168          29 :         case FGFT_STRING:
    2169             :         {
    2170          29 :             wchar_t awsVal[MAX_CAR_COUNT_INDEXED_STR + 1] = {0};
    2171         725 :             for (int j = 0; j < nStrLen; j++)
    2172             :             {
    2173             :                 GUInt16 nCh =
    2174         696 :                     GetUInt16(l_abyPage + m_nOffsetFirstValInPage +
    2175         696 :                                   nStrLen * sizeof(GUInt16) * iFeature,
    2176             :                               j);
    2177         696 :                 awsVal[j] = nCh;
    2178             :             }
    2179          29 :             awsVal[nStrLen] = 0;
    2180             :             char *pszOut =
    2181          29 :                 CPLRecodeFromWChar(awsVal, CPL_ENC_UCS2, CPL_ENC_UTF8);
    2182          29 :             returnErrorIf(pszOut == nullptr);
    2183          29 :             returnErrorAndCleanupIf(strlen(pszOut) >
    2184             :                                         static_cast<size_t>(MAX_UTF8_LEN_STR),
    2185             :                                     VSIFree(pszOut));
    2186          29 :             strcpy(psField->String, pszOut);
    2187          29 :             CPLFree(pszOut);
    2188          29 :             eOutType = OFTString;
    2189          29 :             return psField;
    2190             :         }
    2191             : 
    2192           1 :         case FGFT_GUID:
    2193             :         case FGFT_GLOBALID:
    2194             :         {
    2195           1 :             memcpy(psField->String,
    2196           1 :                    l_abyPage + m_nOffsetFirstValInPage +
    2197           1 :                        UUID_LEN_AS_STRING * iFeature,
    2198             :                    UUID_LEN_AS_STRING);
    2199           1 :             psField->String[UUID_LEN_AS_STRING] = 0;
    2200           1 :             eOutType = OFTString;
    2201           1 :             return psField;
    2202             :         }
    2203             : 
    2204           4 :         case FGFT_INT64:
    2205             :         {
    2206             :             const int64_t nVal =
    2207           4 :                 GetInt64(l_abyPage + m_nOffsetFirstValInPage, iFeature);
    2208           4 :             psField->Integer64 = nVal;
    2209           4 :             eOutType = OFTInteger64;
    2210           4 :             return psField;
    2211             :         }
    2212             : 
    2213           0 :         default:
    2214           0 :             CPLAssert(false);
    2215             :             break;
    2216             :     }
    2217             :     return nullptr;
    2218             : }
    2219             : 
    2220             : /************************************************************************/
    2221             : /*                            GetMinValue()                             */
    2222             : /************************************************************************/
    2223             : 
    2224          14 : const OGRField *FileGDBIndexIterator::GetMinValue(int &eOutType)
    2225             : {
    2226          14 :     if (eOp != FGSO_ISNOTNULL)
    2227           0 :         return FileGDBIterator::GetMinValue(eOutType);
    2228          14 :     if (eFieldType == FGFT_STRING || eFieldType == FGFT_GUID ||
    2229          12 :         eFieldType == FGFT_GLOBALID)
    2230           2 :         sMin.String = szMin;
    2231          14 :     return GetMinMaxValue(&sMin, eOutType, TRUE);
    2232             : }
    2233             : 
    2234             : /************************************************************************/
    2235             : /*                            GetMaxValue()                             */
    2236             : /************************************************************************/
    2237             : 
    2238          36 : const OGRField *FileGDBIndexIterator::GetMaxValue(int &eOutType)
    2239             : {
    2240          36 :     if (eOp != FGSO_ISNOTNULL)
    2241           0 :         return FileGDBIterator::GetMinValue(eOutType);
    2242          36 :     if (eFieldType == FGFT_STRING || eFieldType == FGFT_GUID ||
    2243           7 :         eFieldType == FGFT_GLOBALID)
    2244          29 :         sMax.String = szMax;
    2245          36 :     return GetMinMaxValue(&sMax, eOutType, FALSE);
    2246             : }
    2247             : 
    2248             : /************************************************************************/
    2249             : /*                        GetMinMaxSumCount()                           */
    2250             : /************************************************************************/
    2251             : 
    2252             : struct Int16Getter
    2253             : {
    2254             :   public:
    2255           5 :     static double GetAsDouble(const GByte *pBaseAddr, int iOffset)
    2256             :     {
    2257           5 :         return GetInt16(pBaseAddr, iOffset);
    2258             :     }
    2259             : };
    2260             : 
    2261             : struct Int32Getter
    2262             : {
    2263             :   public:
    2264          15 :     static double GetAsDouble(const GByte *pBaseAddr, int iOffset)
    2265             :     {
    2266          15 :         return GetInt32(pBaseAddr, iOffset);
    2267             :     }
    2268             : };
    2269             : 
    2270             : struct Int64Getter
    2271             : {
    2272             :   public:
    2273           0 :     static double GetAsDouble(const GByte *pBaseAddr, int iOffset)
    2274             :     {
    2275           0 :         return static_cast<double>(GetInt64(pBaseAddr, iOffset));
    2276             :     }
    2277             : };
    2278             : 
    2279             : struct Float32Getter
    2280             : {
    2281             :   public:
    2282           5 :     static double GetAsDouble(const GByte *pBaseAddr, int iOffset)
    2283             :     {
    2284           5 :         return GetFloat32(pBaseAddr, iOffset);
    2285             :     }
    2286             : };
    2287             : 
    2288             : struct Float64Getter
    2289             : {
    2290             :   public:
    2291          10 :     static double GetAsDouble(const GByte *pBaseAddr, int iOffset)
    2292             :     {
    2293          10 :         return GetFloat64(pBaseAddr, iOffset);
    2294             :     }
    2295             : };
    2296             : 
    2297             : template <class Getter>
    2298           8 : void FileGDBIndexIterator::GetMinMaxSumCount(double &dfMin, double &dfMax,
    2299             :                                              double &dfSum, int &nCount)
    2300             : {
    2301           8 :     int nLocalCount = 0;
    2302           8 :     double dfLocalSum = 0.0;
    2303           8 :     double dfVal = 0.0;
    2304             : 
    2305             :     while (true)
    2306             :     {
    2307          43 :         if (iCurFeatureInPage >= nFeaturesInPage)
    2308             :         {
    2309          15 :             if (!LoadNextFeaturePage())
    2310             :             {
    2311           8 :                 break;
    2312             :             }
    2313             :         }
    2314             : 
    2315          35 :         dfVal = Getter::GetAsDouble(abyPageFeature + m_nOffsetFirstValInPage,
    2316             :                                     iCurFeatureInPage);
    2317             : 
    2318          35 :         dfLocalSum += dfVal;
    2319          35 :         if (nLocalCount == 0)
    2320           7 :             dfMin = dfVal;
    2321          35 :         nLocalCount++;
    2322          35 :         iCurFeatureInPage++;
    2323             :     }
    2324             : 
    2325           8 :     dfSum = dfLocalSum;
    2326           8 :     nCount = nLocalCount;
    2327           8 :     dfMax = dfVal;
    2328           8 : }
    2329             : 
    2330           8 : bool FileGDBIndexIterator::GetMinMaxSumCount(double &dfMin, double &dfMax,
    2331             :                                              double &dfSum, int &nCount)
    2332             : {
    2333           8 :     const bool errorRetValue = false;
    2334           8 :     dfMin = 0.0;
    2335           8 :     dfMax = 0.0;
    2336           8 :     dfSum = 0.0;
    2337           8 :     nCount = 0;
    2338           8 :     returnErrorIf(eOp != FGSO_ISNOTNULL);
    2339           8 :     returnErrorIf(eFieldType != FGFT_INT16 && eFieldType != FGFT_INT32 &&
    2340             :                   eFieldType != FGFT_FLOAT32 && eFieldType != FGFT_FLOAT64 &&
    2341             :                   eFieldType != FGFT_DATETIME && eFieldType != FGFT_INT64 &&
    2342             :                   eFieldType != FGFT_DATE && eFieldType != FGFT_TIME &&
    2343             :                   eFieldType != FGFT_DATETIME_WITH_OFFSET);
    2344             : 
    2345           8 :     bool bSaveAscending = bAscending;
    2346           8 :     bAscending = true;
    2347           8 :     Reset();
    2348             : 
    2349           8 :     switch (eFieldType)
    2350             :     {
    2351           1 :         case FGFT_INT16:
    2352             :         {
    2353           1 :             GetMinMaxSumCount<Int16Getter>(dfMin, dfMax, dfSum, nCount);
    2354           1 :             break;
    2355             :         }
    2356           4 :         case FGFT_INT32:
    2357             :         {
    2358           4 :             GetMinMaxSumCount<Int32Getter>(dfMin, dfMax, dfSum, nCount);
    2359           4 :             break;
    2360             :         }
    2361           0 :         case FGFT_INT64:
    2362             :         {
    2363           0 :             GetMinMaxSumCount<Int64Getter>(dfMin, dfMax, dfSum, nCount);
    2364           0 :             break;
    2365             :         }
    2366           1 :         case FGFT_FLOAT32:
    2367             :         {
    2368           1 :             GetMinMaxSumCount<Float32Getter>(dfMin, dfMax, dfSum, nCount);
    2369           1 :             break;
    2370             :         }
    2371           2 :         case FGFT_FLOAT64:
    2372             :         case FGFT_DATETIME:
    2373             :         case FGFT_DATE:
    2374             :         case FGFT_TIME:
    2375             :         case FGFT_DATETIME_WITH_OFFSET:
    2376             :         {
    2377           2 :             GetMinMaxSumCount<Float64Getter>(dfMin, dfMax, dfSum, nCount);
    2378           2 :             break;
    2379             :         }
    2380           0 :         default:
    2381           0 :             CPLAssert(false);
    2382             :             break;
    2383             :     }
    2384             : 
    2385           8 :     bAscending = bSaveAscending;
    2386           8 :     Reset();
    2387             : 
    2388           8 :     return true;
    2389             : }
    2390             : 
    2391             : FileGDBSpatialIndexIterator::~FileGDBSpatialIndexIterator() = default;
    2392             : 
    2393             : /************************************************************************/
    2394             : /*                    FileGDBSpatialIndexIteratorImpl                   */
    2395             : /************************************************************************/
    2396             : 
    2397             : class FileGDBSpatialIndexIteratorImpl final : public FileGDBIndexIteratorBase,
    2398             :                                               public FileGDBSpatialIndexIterator
    2399             : {
    2400             :     OGREnvelope m_sFilterEnvelope;
    2401             :     bool m_bHasBuiltSetFID = false;
    2402             :     std::vector<int64_t> m_oFIDVector{};
    2403             :     size_t m_nVectorIdx = 0;
    2404             :     int m_nGridNo = 0;
    2405             :     GInt64 m_nMinVal = 0;
    2406             :     GInt64 m_nMaxVal = 0;
    2407             :     GInt32 m_nCurX = 0;
    2408             :     GInt32 m_nMaxX = 0;
    2409             : 
    2410             :     virtual bool FindPages(int iLevel, uint64_t nPage) override;
    2411             :     int GetNextRow();
    2412             :     bool ReadNewXRange();
    2413             :     bool ResetInternal();
    2414             :     double GetScaledCoord(double coord) const;
    2415             : 
    2416             :   protected:
    2417             :     friend class FileGDBSpatialIndexIterator;
    2418             : 
    2419             :     FileGDBSpatialIndexIteratorImpl(FileGDBTable *poParent,
    2420             :                                     const OGREnvelope &sFilterEnvelope);
    2421             :     bool Init();
    2422             : 
    2423             :   public:
    2424           2 :     virtual FileGDBTable *GetTable() override
    2425             :     {
    2426           2 :         return poParent;
    2427             :     }  // avoid MSVC C4250 inherits via dominance warning
    2428             : 
    2429             :     virtual int64_t GetNextRowSortedByFID() override;
    2430             :     virtual void Reset() override;
    2431             : 
    2432             :     virtual bool SetEnvelope(const OGREnvelope &sFilterEnvelope) override;
    2433             : };
    2434             : 
    2435             : /************************************************************************/
    2436             : /*                      FileGDBSpatialIndexIteratorImpl()               */
    2437             : /************************************************************************/
    2438             : 
    2439         361 : FileGDBSpatialIndexIteratorImpl::FileGDBSpatialIndexIteratorImpl(
    2440         361 :     FileGDBTable *poParentIn, const OGREnvelope &sFilterEnvelope)
    2441             :     : FileGDBIndexIteratorBase(poParentIn, true),
    2442         361 :       m_sFilterEnvelope(sFilterEnvelope)
    2443             : {
    2444             :     double dfYMinClamped;
    2445             :     double dfYMaxClamped;
    2446         361 :     poParentIn->GetMinMaxProjYForSpatialIndex(dfYMinClamped, dfYMaxClamped);
    2447         361 :     m_sFilterEnvelope.MinY = std::min(
    2448         361 :         std::max(m_sFilterEnvelope.MinY, dfYMinClamped), dfYMaxClamped);
    2449         361 :     m_sFilterEnvelope.MaxY = std::min(
    2450         361 :         std::max(m_sFilterEnvelope.MaxY, dfYMinClamped), dfYMaxClamped);
    2451         361 : }
    2452             : 
    2453             : /************************************************************************/
    2454             : /*                                  Build()                             */
    2455             : /************************************************************************/
    2456             : 
    2457             : FileGDBSpatialIndexIterator *
    2458         361 : FileGDBSpatialIndexIterator::Build(FileGDBTable *poParent,
    2459             :                                    const OGREnvelope &sFilterEnvelope)
    2460             : {
    2461             :     FileGDBSpatialIndexIteratorImpl *poIterator =
    2462         361 :         new FileGDBSpatialIndexIteratorImpl(poParent, sFilterEnvelope);
    2463         361 :     if (!poIterator->Init())
    2464             :     {
    2465         252 :         delete poIterator;
    2466         252 :         return nullptr;
    2467             :     }
    2468         109 :     return poIterator;
    2469             : }
    2470             : 
    2471             : /************************************************************************/
    2472             : /*                         SetEnvelope()                                */
    2473             : /************************************************************************/
    2474             : 
    2475        1068 : bool FileGDBSpatialIndexIteratorImpl::SetEnvelope(
    2476             :     const OGREnvelope &sFilterEnvelope)
    2477             : {
    2478        1068 :     m_sFilterEnvelope = sFilterEnvelope;
    2479        1068 :     m_bHasBuiltSetFID = false;
    2480        1068 :     m_oFIDVector.clear();
    2481        1068 :     return ResetInternal();
    2482             : }
    2483             : 
    2484             : /************************************************************************/
    2485             : /*                              Init()                                  */
    2486             : /************************************************************************/
    2487             : 
    2488         361 : bool FileGDBSpatialIndexIteratorImpl::Init()
    2489             : {
    2490         361 :     const bool errorRetValue = false;
    2491             : 
    2492             :     const std::string osSpxName = CPLFormFilenameSafe(
    2493         722 :         CPLGetPathSafe(poParent->GetFilename().c_str()).c_str(),
    2494        1083 :         CPLGetBasenameSafe(poParent->GetFilename().c_str()).c_str(), "spx");
    2495             : 
    2496         361 :     if (!ReadTrailer(osSpxName.c_str()))
    2497         192 :         return false;
    2498             : 
    2499         169 :     returnErrorIf(m_nValueSize != sizeof(uint64_t));
    2500             : 
    2501         220 :     const auto IsPositiveInt = [](double x) { return x >= 0 && x <= INT_MAX; };
    2502             : 
    2503         169 :     const auto &gridRes = poParent->GetSpatialIndexGridResolution();
    2504         169 :     const FileGDBGeomField *poGDBGeomField = poParent->GetGeomField();
    2505         389 :     if (gridRes.empty() || !(gridRes[0] > 0) ||
    2506             :         // Check if the center of the layer extent results in valid scaled
    2507             :         // coords
    2508         110 :         !(!std::isnan(poGDBGeomField->GetXMin()) &&
    2509         110 :           IsPositiveInt(GetScaledCoord(
    2510         110 :               0.5 * (poGDBGeomField->GetXMin() + poGDBGeomField->GetXMax()))) &&
    2511         110 :           IsPositiveInt(GetScaledCoord(
    2512         110 :               0.5 * (poGDBGeomField->GetYMin() + poGDBGeomField->GetYMax())))))
    2513             :     {
    2514             :         // gridRes[0] == 1.61271680278378622e-312 happens on layer
    2515             :         // Zone18_2014_01_Broadcast of
    2516             :         // https://coast.noaa.gov/htdata/CMSP/AISDataHandler/2014/01/Zone18_2014_01.zip
    2517             :         // The FileGDB driver does not use the .spx file in that situation,
    2518             :         // so do we.
    2519          59 :         CPLDebug("OpenFileGDB",
    2520             :                  "Cannot use %s as the grid resolution is invalid",
    2521             :                  osSpxName.c_str());
    2522          59 :         return false;
    2523             :     }
    2524             : 
    2525             :     // Detect broken .spx file such as SWISSTLM3D_2022_LV95_LN02.gdb/a00000019.spx
    2526             :     // from https://data.geo.admin.ch/ch.swisstopo.swisstlm3d/swisstlm3d_2022-03/swisstlm3d_2022-03_2056_5728.gdb.zip
    2527             :     // which advertises nIndexDepth == 1 whereas it seems to be it should be 2.
    2528         110 :     if (nIndexDepth == 1)
    2529             :     {
    2530         108 :         iLastPageIdx[0] = 0;
    2531         108 :         LoadNextFeaturePage();
    2532         108 :         iFirstPageIdx[0] = iLastPageIdx[0] = -1;
    2533         323 :         if (nFeaturesInPage >= 2 &&
    2534         109 :             nFeaturesInPage < poParent->GetTotalRecordCount() / 10 &&
    2535           1 :             m_nPageCount > static_cast<GUInt32>(nFeaturesInPage))
    2536             :         {
    2537             :             // Check if it looks like a non-feature page, that is that the
    2538             :             // IDs pointed by it are index page IDs and not feature IDs.
    2539           1 :             bool bReferenceOtherPages = true;
    2540           8 :             for (int i = 0; i < nFeaturesInPage; ++i)
    2541             :             {
    2542           7 :                 const GUInt32 nID = GetUInt32(abyPageFeature + 8, i);
    2543           7 :                 if (!(nID >= 2 && nID <= m_nPageCount))
    2544             :                 {
    2545           0 :                     bReferenceOtherPages = false;
    2546           0 :                     break;
    2547             :                 }
    2548             :             }
    2549           1 :             if (bReferenceOtherPages)
    2550             :             {
    2551           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2552             :                          "Cannot use %s as the index depth(=1) is suspicious "
    2553             :                          "(it should rather be 2)",
    2554             :                          osSpxName.c_str());
    2555           1 :                 return false;
    2556             :             }
    2557             :         }
    2558             :     }
    2559             : 
    2560         109 :     return ResetInternal();
    2561             : }
    2562             : 
    2563             : /************************************************************************/
    2564             : /*                         GetScaledCoord()                             */
    2565             : /************************************************************************/
    2566             : 
    2567       22426 : double FileGDBSpatialIndexIteratorImpl::GetScaledCoord(double coord) const
    2568             : {
    2569       22426 :     const auto &gridRes = poParent->GetSpatialIndexGridResolution();
    2570       22426 :     return (coord / gridRes[0] + (1 << 29)) / (gridRes[m_nGridNo] / gridRes[0]);
    2571             : }
    2572             : 
    2573             : /************************************************************************/
    2574             : /*                         ReadNewXRange()                              */
    2575             : /************************************************************************/
    2576             : 
    2577        7243 : bool FileGDBSpatialIndexIteratorImpl::ReadNewXRange()
    2578             : {
    2579             :     const GUInt64 v1 =
    2580        7243 :         (static_cast<GUInt64>(m_nGridNo) << 62) |
    2581       14486 :         (static_cast<GUInt64>(m_nCurX) << 31) |
    2582        7243 :         (static_cast<GUInt64>(
    2583       21729 :             std::min(std::max(0.0, GetScaledCoord(m_sFilterEnvelope.MinY)),
    2584        7243 :                      static_cast<double>(INT_MAX))));
    2585             :     const GUInt64 v2 =
    2586        7243 :         (static_cast<GUInt64>(m_nGridNo) << 62) |
    2587       14486 :         (static_cast<GUInt64>(m_nCurX) << 31) |
    2588        7243 :         (static_cast<GUInt64>(
    2589       21729 :             std::min(std::max(0.0, GetScaledCoord(m_sFilterEnvelope.MaxY)),
    2590        7243 :                      static_cast<double>(INT_MAX))));
    2591        7243 :     if (m_nGridNo < 2)
    2592             :     {
    2593        7243 :         m_nMinVal = v1;
    2594        7243 :         m_nMaxVal = v2;
    2595             :     }
    2596             :     else
    2597             :     {
    2598             :         // Reverse order due to negative sign
    2599           0 :         memcpy(&m_nMinVal, &v2, sizeof(GInt64));
    2600           0 :         memcpy(&m_nMaxVal, &v1, sizeof(GInt64));
    2601             :     }
    2602             : 
    2603        7243 :     const bool errorRetValue = false;
    2604        7243 :     if (m_nValueCountInIdx > 0)
    2605             :     {
    2606        7243 :         if (nIndexDepth == 1)
    2607             :         {
    2608        2859 :             iFirstPageIdx[0] = iLastPageIdx[0] = 0;
    2609             :         }
    2610             :         else
    2611             :         {
    2612        4384 :             returnErrorIf(!FindPages(0, 1));
    2613             :         }
    2614             :     }
    2615             : 
    2616        7243 :     FileGDBIndexIteratorBase::Reset();
    2617             : 
    2618        7243 :     return true;
    2619             : }
    2620             : 
    2621             : /************************************************************************/
    2622             : /*                         FindMinMaxIdx()                              */
    2623             : /************************************************************************/
    2624             : 
    2625        4906 : static bool FindMinMaxIdx(const GByte *pBaseAddr, const int nVals,
    2626             :                           const GInt64 nMinVal, const GInt64 nMaxVal,
    2627             :                           int &minIdxOut, int &maxIdxOut)
    2628             : {
    2629             :     // Find maximum index that is <= nMaxVal
    2630        4906 :     int nMinIdx = 0;
    2631        4906 :     int nMaxIdx = nVals - 1;
    2632       41108 :     while (nMaxIdx - nMinIdx >= 2)
    2633             :     {
    2634       36202 :         int nIdx = (nMinIdx + nMaxIdx) / 2;
    2635       36202 :         const GInt64 nVal = GetInt64(pBaseAddr, nIdx);
    2636       36202 :         if (nVal <= nMaxVal)
    2637        5022 :             nMinIdx = nIdx;
    2638             :         else
    2639       31180 :             nMaxIdx = nIdx;
    2640             :     }
    2641        9401 :     while (GetInt64(pBaseAddr, nMaxIdx) > nMaxVal)
    2642             :     {
    2643        8244 :         nMaxIdx--;
    2644        8244 :         if (nMaxIdx < 0)
    2645             :         {
    2646        3749 :             return false;
    2647             :         }
    2648             :     }
    2649        1157 :     maxIdxOut = nMaxIdx;
    2650             : 
    2651             :     // Find minimum index that is >= nMinVal
    2652        1157 :     nMinIdx = 0;
    2653        8806 :     while (nMaxIdx - nMinIdx >= 2)
    2654             :     {
    2655        7649 :         int nIdx = (nMinIdx + nMaxIdx) / 2;
    2656        7649 :         const GInt64 nVal = GetInt64(pBaseAddr, nIdx);
    2657        7649 :         if (nVal >= nMinVal)
    2658        3013 :             nMaxIdx = nIdx;
    2659             :         else
    2660        4636 :             nMinIdx = nIdx;
    2661             :     }
    2662        2180 :     while (GetInt64(pBaseAddr, nMinIdx) < nMinVal)
    2663             :     {
    2664        1023 :         nMinIdx++;
    2665        1023 :         if (nMinIdx == nVals)
    2666             :         {
    2667           0 :             return false;
    2668             :         }
    2669             :     }
    2670        1157 :     minIdxOut = nMinIdx;
    2671        1157 :     return true;
    2672             : }
    2673             : 
    2674             : /************************************************************************/
    2675             : /*                             FindPages()                              */
    2676             : /************************************************************************/
    2677             : 
    2678        7734 : bool FileGDBSpatialIndexIteratorImpl::FindPages(int iLevel, uint64_t nPage)
    2679             : {
    2680        7734 :     const bool errorRetValue = false;
    2681             : 
    2682        7734 :     iFirstPageIdx[iLevel] = iLastPageIdx[iLevel] = -1;
    2683             : 
    2684             :     const cpl::NonCopyableVector<GByte> *cachedPagePtr =
    2685        7734 :         m_oCachePage[iLevel].getPtr(nPage);
    2686        7734 :     if (cachedPagePtr)
    2687             :     {
    2688        7730 :         memcpy(abyPage[iLevel], cachedPagePtr->data(), m_nPageSize);
    2689             :     }
    2690             :     else
    2691             :     {
    2692           4 :         cpl::NonCopyableVector<GByte> cachedPage;
    2693           4 :         if (m_oCachePage[iLevel].size() == m_oCachePage[iLevel].getMaxSize())
    2694             :         {
    2695           0 :             m_oCachePage[iLevel].removeAndRecycleOldestEntry(cachedPage);
    2696           0 :             cachedPage.clear();
    2697             :         }
    2698             : 
    2699           4 :         VSIFSeekL(fpCurIdx, static_cast<vsi_l_offset>(nPage - 1) * m_nPageSize,
    2700             :                   SEEK_SET);
    2701             : #ifdef DEBUG
    2702           4 :         iLoadedPage[iLevel] = nPage;
    2703             : #endif
    2704           4 :         returnErrorIf(VSIFReadL(abyPage[iLevel], m_nPageSize, 1, fpCurIdx) !=
    2705             :                       1);
    2706           0 :         cachedPage.insert(cachedPage.end(), abyPage[iLevel],
    2707           4 :                           abyPage[iLevel] + m_nPageSize);
    2708           4 :         m_oCachePage[iLevel].insert(nPage, std::move(cachedPage));
    2709             :     }
    2710             : 
    2711        7734 :     nSubPagesCount[iLevel] = GetUInt32(abyPage[iLevel] + m_nObjectIDSize, 0);
    2712        7734 :     returnErrorIf(nSubPagesCount[iLevel] == 0 ||
    2713             :                   nSubPagesCount[iLevel] > nMaxPerPages);
    2714             : 
    2715        7734 :     if (GetInt64(abyPage[iLevel] + m_nOffsetFirstValInPage, 0) > m_nMaxVal)
    2716             :     {
    2717        7700 :         iFirstPageIdx[iLevel] = 0;
    2718             :         // nSubPagesCount[iLevel] == 1 && GetUInt32(abyPage[iLevel] + m_nLeafPageHeaderSize, 0) ==
    2719             :         // 0 should only happen on non-nominal cases where one forces the depth
    2720             :         // of the index to be greater than needed.
    2721        7700 :         if (m_nVersion == 1)
    2722             :         {
    2723        7700 :             iLastPageIdx[iLevel] =
    2724        7700 :                 (nSubPagesCount[iLevel] == 1 &&
    2725        4358 :                  GetUInt32(abyPage[iLevel] + m_nLeafPageHeaderSize, 0) == 0)
    2726       12058 :                     ? 0
    2727             :                     : 1;
    2728             :         }
    2729             :         else
    2730             :         {
    2731           0 :             iLastPageIdx[iLevel] =
    2732           0 :                 (nSubPagesCount[iLevel] == 1 &&
    2733           0 :                  GetUInt64(abyPage[iLevel] + m_nLeafPageHeaderSize, 0) == 0)
    2734           0 :                     ? 0
    2735             :                     : 1;
    2736             :         }
    2737             :     }
    2738          34 :     else if (!FindMinMaxIdx(abyPage[iLevel] + m_nOffsetFirstValInPage,
    2739          34 :                             static_cast<int>(nSubPagesCount[iLevel]), m_nMinVal,
    2740          34 :                             m_nMaxVal, iFirstPageIdx[iLevel],
    2741          34 :                             iLastPageIdx[iLevel]))
    2742             :     {
    2743           0 :         iFirstPageIdx[iLevel] = iLastPageIdx[iLevel] = nSubPagesCount[iLevel];
    2744             :     }
    2745          34 :     else if (iLastPageIdx[iLevel] < static_cast<int>(nSubPagesCount[iLevel]))
    2746             :     {
    2747             :         // Candidate values might extend to the following sub-page
    2748          34 :         iLastPageIdx[iLevel]++;
    2749             :     }
    2750             : 
    2751        7734 :     return true;
    2752             : }
    2753             : 
    2754             : /************************************************************************/
    2755             : /*                              GetNextRow()                            */
    2756             : /************************************************************************/
    2757             : 
    2758       21144 : int FileGDBSpatialIndexIteratorImpl::GetNextRow()
    2759             : {
    2760       21144 :     const int errorRetValue = -1;
    2761       21144 :     if (bEOF)
    2762           0 :         return -1;
    2763             : 
    2764             :     while (true)
    2765             :     {
    2766       24527 :         if (iCurFeatureInPage >= nFeaturesInPage)
    2767             :         {
    2768        5640 :             int nMinIdx = 0;
    2769        5640 :             int nMaxIdx = 0;
    2770        5640 :             if (!LoadNextFeaturePage() ||
    2771        4872 :                 !FindMinMaxIdx(abyPageFeature + m_nOffsetFirstValInPage,
    2772             :                                nFeaturesInPage, m_nMinVal, m_nMaxVal, nMinIdx,
    2773       10512 :                                nMaxIdx) ||
    2774        1123 :                 nMinIdx > nMaxIdx)
    2775             :             {
    2776        4517 :                 if (m_nCurX < m_nMaxX)
    2777             :                 {
    2778        3383 :                     m_nCurX++;
    2779        3383 :                     if (ReadNewXRange())
    2780        3383 :                         continue;
    2781             :                 }
    2782             :                 else
    2783             :                 {
    2784             :                     const auto &gridRes =
    2785        1134 :                         poParent->GetSpatialIndexGridResolution();
    2786        1134 :                     if (m_nGridNo + 1 < static_cast<int>(gridRes.size()) &&
    2787           0 :                         gridRes[m_nGridNo + 1] > 0)
    2788             :                     {
    2789           0 :                         m_nGridNo++;
    2790           0 :                         m_nCurX = static_cast<GInt32>(std::min(
    2791           0 :                             std::max(0.0,
    2792           0 :                                      GetScaledCoord(m_sFilterEnvelope.MinX)),
    2793           0 :                             static_cast<double>(INT_MAX)));
    2794           0 :                         m_nMaxX = static_cast<GInt32>(std::min(
    2795           0 :                             std::max(0.0,
    2796           0 :                                      GetScaledCoord(m_sFilterEnvelope.MaxX)),
    2797           0 :                             static_cast<double>(INT_MAX)));
    2798           0 :                         if (ReadNewXRange())
    2799           0 :                             continue;
    2800             :                     }
    2801             :                 }
    2802             : 
    2803        1134 :                 bEOF = true;
    2804        1134 :                 return -1;
    2805             :             }
    2806             : 
    2807        1123 :             iCurFeatureInPage = nMinIdx;
    2808        1123 :             nFeaturesInPage = nMaxIdx + 1;
    2809             :         }
    2810             : 
    2811             : #ifdef DEBUG
    2812       20010 :         const GInt64 nVal = GetInt64(abyPageFeature + m_nOffsetFirstValInPage,
    2813       20010 :                                      iCurFeatureInPage);
    2814       20010 :         CPL_IGNORE_RET_VAL(nVal);
    2815       20010 :         CPLAssert(nVal >= m_nMinVal && nVal <= m_nMaxVal);
    2816             : #endif
    2817             : 
    2818             :         const GUInt64 nFID =
    2819       20010 :             m_nVersion == 1 ? GetUInt32(abyPageFeature + m_nLeafPageHeaderSize,
    2820             :                                         iCurFeatureInPage)
    2821           7 :                             : GetUInt64(abyPageFeature + m_nLeafPageHeaderSize,
    2822       20010 :                                         iCurFeatureInPage);
    2823       20010 :         iCurFeatureInPage++;
    2824       20010 :         returnErrorAndCleanupIf(
    2825             :             nFID < 1 ||
    2826             :                 nFID > static_cast<GUInt64>(poParent->GetTotalRecordCount()),
    2827             :             bEOF = true);
    2828       20010 :         return static_cast<int>(nFID - 1);
    2829        3383 :     }
    2830             : }
    2831             : 
    2832             : /************************************************************************/
    2833             : /*                             Reset()                                  */
    2834             : /************************************************************************/
    2835             : 
    2836        3860 : bool FileGDBSpatialIndexIteratorImpl::ResetInternal()
    2837             : {
    2838        3860 :     m_nGridNo = 0;
    2839             : 
    2840        3860 :     const auto &gridRes = poParent->GetSpatialIndexGridResolution();
    2841        7720 :     if (gridRes.empty() ||  // shouldn't happen
    2842        3860 :         !(gridRes[0] > 0))
    2843             :     {
    2844           0 :         return false;
    2845             :     }
    2846             : 
    2847        3860 :     m_nCurX = static_cast<GInt32>(
    2848       11580 :         std::min(std::max(0.0, GetScaledCoord(m_sFilterEnvelope.MinX)),
    2849        3860 :                  static_cast<double>(INT_MAX)));
    2850        3860 :     m_nMaxX = static_cast<GInt32>(
    2851       11580 :         std::min(std::max(0.0, GetScaledCoord(m_sFilterEnvelope.MaxX)),
    2852        3860 :                  static_cast<double>(INT_MAX)));
    2853        3860 :     m_nVectorIdx = 0;
    2854        3860 :     return ReadNewXRange();
    2855             : }
    2856             : 
    2857        2683 : void FileGDBSpatialIndexIteratorImpl::Reset()
    2858             : {
    2859        2683 :     ResetInternal();
    2860        2683 : }
    2861             : 
    2862             : /************************************************************************/
    2863             : /*                        GetNextRowSortedByFID()                       */
    2864             : /************************************************************************/
    2865             : 
    2866       10520 : int64_t FileGDBSpatialIndexIteratorImpl::GetNextRowSortedByFID()
    2867             : {
    2868       10520 :     if (m_nVectorIdx == 0)
    2869             :     {
    2870        1254 :         if (!m_bHasBuiltSetFID)
    2871             :         {
    2872        1134 :             m_bHasBuiltSetFID = true;
    2873             :             // Accumulating in a vector and sorting is measurably faster
    2874             :             // than using a unordered_set (or set)
    2875             :             while (true)
    2876             :             {
    2877       21144 :                 const auto nFID = GetNextRow();
    2878       21144 :                 if (nFID < 0)
    2879        1134 :                     break;
    2880       20010 :                 m_oFIDVector.push_back(nFID);
    2881       20010 :             }
    2882        1134 :             std::sort(m_oFIDVector.begin(), m_oFIDVector.end());
    2883             :         }
    2884             : 
    2885        1254 :         if (m_oFIDVector.empty())
    2886         143 :             return -1;
    2887        1111 :         const auto nFID = m_oFIDVector[m_nVectorIdx];
    2888        1111 :         ++m_nVectorIdx;
    2889        1111 :         return nFID;
    2890             :     }
    2891             : 
    2892        9266 :     const auto nLastFID = m_oFIDVector[m_nVectorIdx - 1];
    2893        9783 :     while (m_nVectorIdx < m_oFIDVector.size())
    2894             :     {
    2895             :         // Do not return consecutive identical FID
    2896        9698 :         const auto nFID = m_oFIDVector[m_nVectorIdx];
    2897        9698 :         ++m_nVectorIdx;
    2898        9698 :         if (nFID == nLastFID)
    2899             :         {
    2900         517 :             continue;
    2901             :         }
    2902        9181 :         return nFID;
    2903             :     }
    2904          85 :     return -1;
    2905             : }
    2906             : 
    2907             : } /* namespace OpenFileGDB */

Generated by: LCOV version 1.14