Coverage Report

Created: 2025-08-11 09:23

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