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