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