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