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