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