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

Generated by: LCOV version 1.14