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

Generated by: LCOV version 1.14