Coverage Report

Created: 2026-06-30 08:33

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