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

Generated by: LCOV version 1.14