LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/openfilegdb - filegdbtable_write.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1180 1377 85.7 %
Date: 2024-05-02 22:57:13 Functions: 24 24 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Implements writing of FileGDB tables
       5             :  * Author:   Even Rouault, <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2022, 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             : 
      31             : #include "gdal_version_full/gdal_version.h"
      32             : 
      33             : #include "filegdbtable.h"
      34             : 
      35             : #include <algorithm>
      36             : #include <cwchar>
      37             : #include <errno.h>
      38             : #include <limits.h>
      39             : #include <stddef.h>
      40             : #include <stdio.h>
      41             : #include <string.h>
      42             : #include <time.h>
      43             : #include <limits>
      44             : #include <set>
      45             : #include <string>
      46             : #include <vector>
      47             : 
      48             : #include "cpl_conv.h"
      49             : #include "cpl_error.h"
      50             : #include "cpl_string.h"
      51             : #include "cpl_time.h"
      52             : #include "cpl_vsi.h"
      53             : #include "filegdbtable_priv.h"
      54             : #include "ogr_api.h"
      55             : #include "ogr_core.h"
      56             : #include "ogr_geometry.h"
      57             : #include "ogrpgeogeometry.h"
      58             : 
      59             : namespace OpenFileGDB
      60             : {
      61             : 
      62             : constexpr uint8_t EXT_SHAPE_SEGMENT_ARC = 1;
      63             : constexpr int TABLX_HEADER_SIZE = 16;
      64             : constexpr int TABLX_FEATURES_PER_PAGE = 1024;
      65             : 
      66             : /************************************************************************/
      67             : /*                               Create()                               */
      68             : /************************************************************************/
      69             : 
      70        1840 : bool FileGDBTable::Create(const char *pszFilename, int nTablxOffsetSize,
      71             :                           FileGDBTableGeometryType eTableGeomType,
      72             :                           bool bGeomTypeHasZ, bool bGeomTypeHasM)
      73             : {
      74        1840 :     CPLAssert(m_fpTable == nullptr);
      75             : 
      76        1840 :     m_bUpdate = true;
      77        1840 :     m_eTableGeomType = eTableGeomType;
      78        1840 :     m_nTablxOffsetSize = nTablxOffsetSize;
      79        1840 :     m_bGeomTypeHasZ = bGeomTypeHasZ;
      80        1840 :     m_bGeomTypeHasM = bGeomTypeHasM;
      81        1840 :     m_bHasReadGDBIndexes = TRUE;
      82             : 
      83        1840 :     if (!EQUAL(CPLGetExtension(pszFilename), "gdbtable"))
      84             :     {
      85           0 :         CPLError(CE_Failure, CPLE_AppDefined,
      86             :                  "FileGDB table extension must be gdbtable");
      87           0 :         return false;
      88             :     }
      89             : 
      90        1840 :     m_osFilename = pszFilename;
      91        1840 :     m_fpTable = VSIFOpenL(pszFilename, "wb+");
      92        1840 :     if (m_fpTable == nullptr)
      93             :     {
      94           0 :         CPLError(CE_Failure, CPLE_OpenFailed, "Cannot create %s: %s",
      95           0 :                  m_osFilename.c_str(), VSIStrerror(errno));
      96           0 :         return false;
      97             :     }
      98             : 
      99        3680 :     const std::string osTableXName = CPLResetExtension(pszFilename, "gdbtablx");
     100        1840 :     m_fpTableX = VSIFOpenL(osTableXName.c_str(), "wb+");
     101        1840 :     if (m_fpTableX == nullptr)
     102             :     {
     103           0 :         CPLError(CE_Failure, CPLE_OpenFailed, "Cannot create %s: %s",
     104           0 :                  osTableXName.c_str(), VSIStrerror(errno));
     105           0 :         return false;
     106             :     }
     107             : 
     108        1840 :     if (!WriteHeader(m_fpTable))
     109           0 :         return false;
     110             : 
     111        1840 :     if (!WriteHeaderX(m_fpTableX))
     112           0 :         return false;
     113             : 
     114        1840 :     m_bDirtyTableXTrailer = true;
     115             : 
     116        1840 :     return true;
     117             : }
     118             : 
     119             : /************************************************************************/
     120             : /*                          SetTextUTF16()                              */
     121             : /************************************************************************/
     122             : 
     123           1 : bool FileGDBTable::SetTextUTF16()
     124             : {
     125           1 :     if (m_nOffsetFieldDesc != 0)
     126             :     {
     127           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     128             :                  "SetTextUTF16() should be called immediately after Create()");
     129           0 :         return false;
     130             :     }
     131             : 
     132           1 :     m_bStringsAreUTF8 = false;
     133           1 :     return true;
     134             : }
     135             : 
     136             : /************************************************************************/
     137             : /*                           WriteHeader()                              */
     138             : /************************************************************************/
     139             : 
     140        1885 : bool FileGDBTable::WriteHeader(VSILFILE *fpTable)
     141             : {
     142             :     // Could be useful in case we get something wrong...
     143             :     const char *pszCreator =
     144        1885 :         CPLGetConfigOption("OPENFILEGDB_CREATOR", "GDAL " GDAL_RELEASE_NAME);
     145             : 
     146        1885 :     m_nFileSize = 0;
     147        1885 :     m_bDirtyHeader = true;
     148        1885 :     m_bDirtyFieldDescriptors = true;
     149        1885 :     m_nOffsetFieldDesc = 0;
     150        1885 :     m_nFieldDescLength = 0;
     151             : 
     152        1885 :     VSIFSeekL(fpTable, 0, SEEK_SET);
     153             : 
     154             :     bool bRet =
     155        1885 :         WriteUInt32(fpTable, 3) &&                    // version number
     156        1885 :         WriteUInt32(fpTable, m_nValidRecordCount) &&  // number of valid rows
     157        1885 :         WriteUInt32(fpTable,
     158        1885 :                     m_nHeaderBufferMaxSize) &&  // largest size of a feature
     159             :                                                 // record / field description
     160        1885 :         WriteUInt32(fpTable, 5) &&              // magic value
     161        1885 :         WriteUInt32(fpTable, 0) &&              // magic value
     162        1885 :         WriteUInt32(fpTable, 0) &&              // magic value
     163        5655 :         WriteUInt64(fpTable, m_nFileSize) &&
     164        1885 :         WriteUInt64(fpTable, m_nOffsetFieldDesc);
     165             : 
     166        1885 :     if (bRet && pszCreator[0] != '\0')
     167             :     {
     168             :         // Writing the creator is not part of the "spec", but we just use
     169             :         // the fact that there might be ghost areas in the file
     170        1885 :         bRet =
     171        3770 :             WriteUInt32(fpTable, static_cast<uint32_t>(strlen(pszCreator))) &&
     172        1885 :             VSIFWriteL(pszCreator, strlen(pszCreator), 1, fpTable) == 1;
     173             :     }
     174             : 
     175        1885 :     if (!bRet)
     176             :     {
     177           0 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot write .gdbtable header");
     178           0 :         return false;
     179             :     }
     180             : 
     181        1885 :     m_nFileSize = VSIFTellL(fpTable);
     182        1885 :     return true;
     183             : }
     184             : 
     185             : /************************************************************************/
     186             : /*                           WriteHeaderX()                             */
     187             : /************************************************************************/
     188             : 
     189        1871 : bool FileGDBTable::WriteHeaderX(VSILFILE *fpTableX)
     190             : {
     191        1871 :     VSIFSeekL(fpTableX, 0, SEEK_SET);
     192        1871 :     if (!WriteUInt32(fpTableX, 3) ||  // version number
     193        1871 :         !WriteUInt32(fpTableX, m_n1024BlocksPresent) ||
     194        5613 :         !WriteUInt32(fpTableX, m_nTotalRecordCount) ||
     195        1871 :         !WriteUInt32(fpTableX, m_nTablxOffsetSize))
     196             :     {
     197           0 :         CPLError(CE_Failure, CPLE_FileIO, "Cannot write .gdbtablx header");
     198           0 :         return false;
     199             :     }
     200        1871 :     return true;
     201             : }
     202             : 
     203             : /************************************************************************/
     204             : /*                                Sync()                                */
     205             : /************************************************************************/
     206             : 
     207       10717 : bool FileGDBTable::Sync(VSILFILE *fpTable, VSILFILE *fpTableX)
     208             : {
     209       10717 :     if (!m_bUpdate)
     210        3694 :         return true;
     211             : 
     212        7023 :     if (fpTable == nullptr)
     213        6939 :         fpTable = m_fpTable;
     214             : 
     215        7023 :     if (fpTableX == nullptr)
     216        6939 :         fpTableX = m_fpTableX;
     217             : 
     218        7023 :     bool bRet = true;
     219             : 
     220        7023 :     if (m_bDirtyGdbIndexesFile)
     221             :     {
     222         268 :         m_bDirtyGdbIndexesFile = false;
     223         268 :         CreateGdbIndexesFile();
     224             :     }
     225             : 
     226        7023 :     if (m_bDirtyIndices)
     227             :     {
     228        2655 :         m_bDirtyIndices = false;
     229        2655 :         RefreshIndices();
     230             :     }
     231             : 
     232        7023 :     if (m_bDirtyFieldDescriptors && fpTable)
     233         635 :         bRet = WriteFieldDescriptors(fpTable);
     234             : 
     235        7023 :     if (m_bDirtyGeomFieldBBox && fpTable)
     236             :     {
     237         120 :         VSIFSeekL(fpTable, m_nOffsetFieldDesc + m_nGeomFieldBBoxSubOffset,
     238             :                   SEEK_SET);
     239         120 :         const auto poGeomField = cpl::down_cast<const FileGDBGeomField *>(
     240         120 :             m_apoFields[m_iGeomField].get());
     241         120 :         bRet &= WriteFloat64(fpTable, poGeomField->GetXMin());
     242         120 :         bRet &= WriteFloat64(fpTable, poGeomField->GetYMin());
     243         120 :         bRet &= WriteFloat64(fpTable, poGeomField->GetXMax());
     244         120 :         bRet &= WriteFloat64(fpTable, poGeomField->GetYMax());
     245         120 :         if (m_bGeomTypeHasZ)
     246             :         {
     247          33 :             bRet &= WriteFloat64(fpTable, poGeomField->GetZMin());
     248          33 :             bRet &= WriteFloat64(fpTable, poGeomField->GetZMax());
     249             :         }
     250         120 :         m_bDirtyGeomFieldBBox = false;
     251             :     }
     252             : 
     253        7023 :     if (m_bDirtyGeomFieldSpatialIndexGridRes && fpTable)
     254             :     {
     255          95 :         VSIFSeekL(fpTable,
     256          95 :                   m_nOffsetFieldDesc + m_nGeomFieldSpatialIndexGridResSubOffset,
     257             :                   SEEK_SET);
     258          95 :         const auto poGeomField = cpl::down_cast<const FileGDBGeomField *>(
     259          95 :             m_apoFields[m_iGeomField].get());
     260             :         const auto &adfSpatialIndexGridResolution =
     261          95 :             poGeomField->GetSpatialIndexGridResolution();
     262         190 :         for (double dfSize : adfSpatialIndexGridResolution)
     263          95 :             bRet &= WriteFloat64(fpTable, dfSize);
     264          95 :         m_bDirtyGeomFieldSpatialIndexGridRes = false;
     265             :     }
     266             : 
     267        7023 :     if (m_bDirtyHeader && fpTable)
     268             :     {
     269        3283 :         VSIFSeekL(fpTable, 4, SEEK_SET);
     270        3283 :         bRet &= WriteUInt32(fpTable, m_nValidRecordCount);
     271        3283 :         m_nHeaderBufferMaxSize =
     272        6566 :             std::max(m_nHeaderBufferMaxSize,
     273        3283 :                      std::max(m_nRowBufferMaxSize, m_nFieldDescLength));
     274        3283 :         bRet &= WriteUInt32(fpTable, m_nHeaderBufferMaxSize);
     275             : 
     276        3283 :         VSIFSeekL(fpTable, 24, SEEK_SET);
     277        3283 :         bRet &= WriteUInt64(fpTable, m_nFileSize);
     278        3283 :         bRet &= WriteUInt64(fpTable, m_nOffsetFieldDesc);
     279             : 
     280        3283 :         VSIFSeekL(fpTable, 0, SEEK_END);
     281        3283 :         CPLAssert(VSIFTellL(fpTable) == m_nFileSize);
     282        3283 :         m_bDirtyHeader = false;
     283             :     }
     284             : 
     285        7023 :     if (m_bDirtyTableXHeader && fpTableX)
     286             :     {
     287        2586 :         VSIFSeekL(fpTableX, 4, SEEK_SET);
     288        2586 :         bRet &= WriteUInt32(fpTableX, m_n1024BlocksPresent);
     289        2586 :         bRet &= WriteUInt32(fpTableX, m_nTotalRecordCount);
     290        2586 :         m_bDirtyTableXHeader = false;
     291             :     }
     292             : 
     293        7023 :     if (m_bDirtyTableXTrailer && fpTableX)
     294             :     {
     295        2352 :         m_nOffsetTableXTrailer =
     296        2352 :             TABLX_HEADER_SIZE +
     297        2352 :             m_nTablxOffsetSize * TABLX_FEATURES_PER_PAGE *
     298        2352 :                 static_cast<vsi_l_offset>(m_n1024BlocksPresent);
     299        2352 :         VSIFSeekL(fpTableX, m_nOffsetTableXTrailer, SEEK_SET);
     300        2352 :         const uint32_t n1024BlocksTotal =
     301        2352 :             DIV_ROUND_UP(m_nTotalRecordCount, TABLX_FEATURES_PER_PAGE);
     302        2352 :         if (!m_abyTablXBlockMap.empty())
     303             :         {
     304          31 :             CPLAssert(m_abyTablXBlockMap.size() >= (n1024BlocksTotal + 7) / 8);
     305             :         }
     306             :         // Size of the bitmap in terms of 32-bit words, rounded to a multiple
     307             :         // of 32.
     308             :         const uint32_t nBitmapInt32Words =
     309        7056 :             DIV_ROUND_UP(
     310             :                 DIV_ROUND_UP(static_cast<uint32_t>(m_abyTablXBlockMap.size()),
     311             :                              4),
     312        7056 :                 32) *
     313        2352 :             32;
     314        2352 :         m_abyTablXBlockMap.resize(nBitmapInt32Words * 4);
     315        2352 :         bRet &= WriteUInt32(fpTableX, nBitmapInt32Words);
     316        2352 :         bRet &= WriteUInt32(fpTableX, n1024BlocksTotal);
     317        2352 :         bRet &= WriteUInt32(fpTableX, m_n1024BlocksPresent);
     318        2352 :         uint32_t nTrailingZero32BitWords = 0;
     319        2352 :         for (int i = static_cast<int>(m_abyTablXBlockMap.size() / 4) - 1;
     320        3251 :              i >= 0; --i)
     321             :         {
     322         930 :             if (m_abyTablXBlockMap[4 * i] != 0 ||
     323         901 :                 m_abyTablXBlockMap[4 * i + 1] != 0 ||
     324        2732 :                 m_abyTablXBlockMap[4 * i + 2] != 0 ||
     325         901 :                 m_abyTablXBlockMap[4 * i + 3] != 0)
     326             :             {
     327          31 :                 break;
     328             :             }
     329         899 :             nTrailingZero32BitWords++;
     330             :         }
     331        2352 :         const uint32_t nLeadingNonZero32BitWords =
     332             :             nBitmapInt32Words - nTrailingZero32BitWords;
     333        2352 :         bRet &= WriteUInt32(fpTableX, nLeadingNonZero32BitWords);
     334        2352 :         if (!m_abyTablXBlockMap.empty())
     335             :         {
     336             : #ifdef DEBUG
     337          31 :             uint32_t nCountBlocks = 0;
     338     4194430 :             for (uint32_t i = 0; i < n1024BlocksTotal; i++)
     339     4194400 :                 nCountBlocks += TEST_BIT(m_abyTablXBlockMap.data(), i) != 0;
     340          31 :             if (nCountBlocks != m_n1024BlocksPresent)
     341             :             {
     342           0 :                 CPLError(
     343             :                     CE_Failure, CPLE_AppDefined,
     344             :                     "Sync(): nCountBlocks(=%u) != m_n1024BlocksPresent(=%u)",
     345             :                     nCountBlocks, m_n1024BlocksPresent);
     346             :             }
     347             : #endif
     348          31 :             bRet &= VSIFWriteL(m_abyTablXBlockMap.data(), 1,
     349             :                                m_abyTablXBlockMap.size(),
     350          31 :                                fpTableX) == m_abyTablXBlockMap.size();
     351             :         }
     352        2352 :         m_bDirtyTableXTrailer = false;
     353             :     }
     354             : 
     355        7023 :     if (m_bFreelistCanBeDeleted)
     356             :     {
     357           6 :         DeleteFreeList();
     358             :     }
     359             : 
     360        7023 :     if (fpTable)
     361        6967 :         VSIFFlushL(fpTable);
     362             : 
     363        7023 :     if (fpTableX)
     364        6967 :         VSIFFlushL(fpTableX);
     365             : 
     366        7023 :     return bRet;
     367             : }
     368             : 
     369             : /************************************************************************/
     370             : /*                          EncodeEnvelope()                            */
     371             : /************************************************************************/
     372             : 
     373             : #define CHECK_CAN_BE_ENCODED_ON_VARUINT(v, msg)                                \
     374             :     if (!((v) >= 0 &&                                                          \
     375             :           (v) <= static_cast<double>(std::numeric_limits<uint64_t>::max())))   \
     376             :     {                                                                          \
     377             :         CPLError(CE_Failure, CPLE_AppDefined, msg);                            \
     378             :         return false;                                                          \
     379             :     }
     380             : 
     381             : #define CHECK_CAN_BE_ENCODED_ON_VARINT(v, oldV, msg)                           \
     382             :     if (!((v) >= static_cast<double>(std::numeric_limits<int64_t>::min()) &&   \
     383             :           (v) <= static_cast<double>(std::numeric_limits<int64_t>::max())))    \
     384             :     {                                                                          \
     385             :         CPLError(CE_Failure, CPLE_AppDefined, msg);                            \
     386             :         return false;                                                          \
     387             :     }                                                                          \
     388             :     if (!(((v) - (oldV)) >=                                                    \
     389             :               static_cast<double>(std::numeric_limits<int64_t>::min()) &&      \
     390             :           ((v) - (oldV)) <=                                                    \
     391             :               static_cast<double>(std::numeric_limits<int64_t>::max())))       \
     392             :     {                                                                          \
     393             :         CPLError(CE_Failure, CPLE_AppDefined, msg);                            \
     394             :         return false;                                                          \
     395             :     }
     396             : 
     397          85 : static bool EncodeEnvelope(std::vector<GByte> &abyBuffer,
     398             :                            const FileGDBGeomField *poGeomField,
     399             :                            const OGRGeometry *poGeom)
     400             : {
     401          85 :     OGREnvelope oEnvelope;
     402          85 :     poGeom->getEnvelope(&oEnvelope);
     403             : 
     404             :     double dfVal;
     405             : 
     406          85 :     dfVal = (oEnvelope.MinX - poGeomField->GetXOrigin()) *
     407          85 :             poGeomField->GetXYScale();
     408          85 :     CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode X value");
     409          85 :     WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
     410             : 
     411          85 :     dfVal = (oEnvelope.MinY - poGeomField->GetYOrigin()) *
     412          85 :             poGeomField->GetXYScale();
     413          85 :     CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode Y value");
     414          85 :     WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
     415             : 
     416          85 :     dfVal = (oEnvelope.MaxX - oEnvelope.MinX) * poGeomField->GetXYScale();
     417          85 :     CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode X value");
     418          85 :     WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
     419             : 
     420          85 :     dfVal = (oEnvelope.MaxY - oEnvelope.MinY) * poGeomField->GetXYScale();
     421          85 :     CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode Y value");
     422          85 :     WriteVarUInt(abyBuffer, static_cast<uint64_t>(dfVal + 0.5));
     423             : 
     424          85 :     return true;
     425             : }
     426             : 
     427             : /************************************************************************/
     428             : /*                          EncodeGeometry()                            */
     429             : /************************************************************************/
     430             : 
     431        3879 : bool FileGDBTable::EncodeGeometry(const FileGDBGeomField *poGeomField,
     432             :                                   const OGRGeometry *poGeom)
     433             : {
     434        3879 :     m_abyGeomBuffer.clear();
     435             : 
     436        3879 :     const auto bIs3D = poGeom->Is3D();
     437        3879 :     const auto bIsMeasured = poGeom->IsMeasured();
     438             : 
     439             :     const auto WriteEndOfCurveOrSurface =
     440       15882 :         [this, bIs3D, bIsMeasured, poGeomField, poGeom](int nCurveDescrCount)
     441             :     {
     442          83 :         WriteVarUInt(m_abyGeomBuffer, static_cast<uint32_t>(m_adfX.size()));
     443          83 :         if (m_adfX.empty())
     444           8 :             return true;
     445          75 :         WriteVarUInt(m_abyGeomBuffer,
     446          75 :                      static_cast<uint32_t>(m_anNumberPointsPerPart.size()));
     447          75 :         if (nCurveDescrCount > 0)
     448          12 :             WriteVarUInt(m_abyGeomBuffer, nCurveDescrCount);
     449             : 
     450          75 :         if (!EncodeEnvelope(m_abyGeomBuffer, poGeomField, poGeom))
     451           0 :             return false;
     452             : 
     453         100 :         for (int iPart = 0;
     454         100 :              iPart < static_cast<int>(m_anNumberPointsPerPart.size()) - 1;
     455             :              ++iPart)
     456             :         {
     457          25 :             WriteVarUInt(m_abyGeomBuffer, m_anNumberPointsPerPart[iPart]);
     458             :         }
     459             : 
     460             :         {
     461          75 :             int64_t nLastX = 0;
     462          75 :             int64_t nLastY = 0;
     463        1647 :             for (size_t i = 0; i < m_adfX.size(); ++i)
     464             :             {
     465             :                 double dfVal =
     466        1572 :                     std::round((m_adfX[i] - poGeomField->GetXOrigin()) *
     467        1572 :                                poGeomField->GetXYScale());
     468        1572 :                 CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastX,
     469             :                                                "Cannot encode X value");
     470        1572 :                 const int64_t nX = static_cast<int64_t>(dfVal);
     471        1572 :                 WriteVarInt(m_abyGeomBuffer, nX - nLastX);
     472             : 
     473        1572 :                 dfVal = std::round((m_adfY[i] - poGeomField->GetYOrigin()) *
     474        1572 :                                    poGeomField->GetXYScale());
     475        1572 :                 CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastY,
     476             :                                                "Cannot encode Y value");
     477        1572 :                 const int64_t nY = static_cast<int64_t>(dfVal);
     478        1572 :                 WriteVarInt(m_abyGeomBuffer, nY - nLastY);
     479             : 
     480        1572 :                 nLastX = nX;
     481        1572 :                 nLastY = nY;
     482             :             }
     483             :         }
     484             : 
     485          75 :         if (bIs3D)
     486             :         {
     487          20 :             int64_t nLastZ = 0;
     488         107 :             for (size_t i = 0; i < m_adfZ.size(); ++i)
     489             :             {
     490             :                 double dfVal =
     491          87 :                     std::round((m_adfZ[i] - poGeomField->GetZOrigin()) *
     492          87 :                                poGeomField->GetZScale());
     493          87 :                 CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastZ,
     494             :                                                "Cannot encode Z value");
     495          87 :                 const int64_t nZ = static_cast<int64_t>(dfVal);
     496          87 :                 WriteVarInt(m_abyGeomBuffer, nZ - nLastZ);
     497             : 
     498          87 :                 nLastZ = nZ;
     499             :             }
     500             :         }
     501             : 
     502          75 :         if (bIsMeasured)
     503             :         {
     504          12 :             int64_t nLastM = 0;
     505          71 :             for (size_t i = 0; i < m_adfM.size(); ++i)
     506             :             {
     507             :                 double dfVal =
     508          59 :                     std::round((m_adfM[i] - poGeomField->GetMOrigin()) *
     509          59 :                                poGeomField->GetMScale());
     510          59 :                 CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastM,
     511             :                                                "Cannot encode M value");
     512          59 :                 const int64_t nM = static_cast<int64_t>(dfVal);
     513          59 :                 WriteVarInt(m_abyGeomBuffer, nM - nLastM);
     514             : 
     515          59 :                 nLastM = nM;
     516             :             }
     517             :         }
     518             : 
     519          75 :         if (!m_abyCurvePart.empty())
     520             :         {
     521          12 :             m_abyGeomBuffer.insert(m_abyGeomBuffer.end(),
     522             :                                    m_abyCurvePart.begin(),
     523          24 :                                    m_abyCurvePart.end());
     524             :         }
     525             : 
     526          75 :         return true;
     527        3879 :     };
     528             : 
     529             :     const auto ReserveXYZMArrays =
     530         588 :         [this, bIs3D, bIsMeasured](const size_t nAdditionalSize)
     531             :     {
     532          79 :         size_t nNewMinSize = m_adfX.size() + nAdditionalSize;
     533          79 :         if (nNewMinSize > m_adfX.capacity())
     534             :         {
     535          65 :             size_t nNewCapacity = nNewMinSize;
     536          65 :             if (m_adfX.capacity() < std::numeric_limits<size_t>::max() / 2)
     537             :             {
     538          65 :                 nNewCapacity = std::max(nNewCapacity, 2 * m_adfX.capacity());
     539             :             }
     540          65 :             m_adfX.reserve(nNewCapacity);
     541          65 :             m_adfY.reserve(nNewCapacity);
     542          65 :             if (bIs3D)
     543          24 :                 m_adfZ.reserve(nNewCapacity);
     544          65 :             if (bIsMeasured)
     545          16 :                 m_adfM.reserve(nNewCapacity);
     546             :         }
     547          79 :     };
     548             : 
     549        3879 :     const auto eFlatType = wkbFlatten(poGeom->getGeometryType());
     550        3879 :     switch (eFlatType)
     551             :     {
     552        3784 :         case wkbPoint:
     553             :         {
     554        3784 :             if (bIs3D)
     555             :             {
     556          14 :                 if (bIsMeasured)
     557             :                 {
     558           4 :                     WriteUInt8(m_abyGeomBuffer,
     559             :                                static_cast<uint8_t>(SHPT_POINTZM));
     560             :                 }
     561             :                 else
     562             :                 {
     563          10 :                     WriteUInt8(m_abyGeomBuffer,
     564             :                                static_cast<uint8_t>(SHPT_POINTZ));
     565             :                 }
     566             :             }
     567             :             else
     568             :             {
     569        3770 :                 if (bIsMeasured)
     570             :                 {
     571           2 :                     WriteUInt8(m_abyGeomBuffer,
     572             :                                static_cast<uint8_t>(SHPT_POINTM));
     573             :                 }
     574             :                 else
     575             :                 {
     576        3768 :                     WriteUInt8(m_abyGeomBuffer,
     577             :                                static_cast<uint8_t>(SHPT_POINT));
     578             :                 }
     579             :             }
     580        3784 :             const auto poPoint = poGeom->toPoint();
     581        3784 :             if (poPoint->IsEmpty())
     582             :             {
     583           4 :                 WriteUInt8(m_abyGeomBuffer, 0);
     584           4 :                 WriteUInt8(m_abyGeomBuffer, 0);
     585           4 :                 if (bIs3D)
     586           2 :                     WriteUInt8(m_abyGeomBuffer, 0);
     587           4 :                 if (bIsMeasured)
     588           2 :                     WriteUInt8(m_abyGeomBuffer, 0);
     589             :             }
     590             :             else
     591             :             {
     592             :                 double dfVal;
     593             : 
     594        3780 :                 dfVal = (poPoint->getX() - poGeomField->GetXOrigin()) *
     595        3780 :                             poGeomField->GetXYScale() +
     596             :                         1;
     597        3780 :                 CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode value");
     598        3780 :                 WriteVarUInt(m_abyGeomBuffer,
     599        3780 :                              static_cast<uint64_t>(dfVal + 0.5));
     600             : 
     601        3780 :                 dfVal = (poPoint->getY() - poGeomField->GetYOrigin()) *
     602        3780 :                             poGeomField->GetXYScale() +
     603             :                         1;
     604        3780 :                 CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal, "Cannot encode Y value");
     605        3780 :                 WriteVarUInt(m_abyGeomBuffer,
     606        3780 :                              static_cast<uint64_t>(dfVal + 0.5));
     607             : 
     608        3780 :                 if (bIs3D)
     609             :                 {
     610          12 :                     dfVal = (poPoint->getZ() - poGeomField->GetZOrigin()) *
     611          12 :                                 poGeomField->GetZScale() +
     612             :                             1;
     613          12 :                     CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal,
     614             :                                                     "Cannot encode Z value");
     615          12 :                     WriteVarUInt(m_abyGeomBuffer,
     616          12 :                                  static_cast<uint64_t>(dfVal + 0.5));
     617             :                 }
     618             : 
     619        3780 :                 if (bIsMeasured)
     620             :                 {
     621           4 :                     dfVal = (poPoint->getM() - poGeomField->GetMOrigin()) *
     622           4 :                                 poGeomField->GetMScale() +
     623             :                             1;
     624           4 :                     CHECK_CAN_BE_ENCODED_ON_VARUINT(dfVal,
     625             :                                                     "Cannot encode M value");
     626           4 :                     WriteVarUInt(m_abyGeomBuffer,
     627           4 :                                  static_cast<uint64_t>(dfVal + 0.5));
     628             :                 }
     629             :             }
     630             : 
     631        3784 :             return true;
     632             :         }
     633             : 
     634           9 :         case wkbMultiPoint:
     635             :         {
     636           9 :             if (bIs3D)
     637             :             {
     638           4 :                 if (bIsMeasured)
     639             :                 {
     640           1 :                     WriteUInt8(m_abyGeomBuffer,
     641             :                                static_cast<uint8_t>(SHPT_MULTIPOINTZM));
     642             :                 }
     643             :                 else
     644             :                 {
     645           3 :                     WriteUInt8(m_abyGeomBuffer,
     646             :                                static_cast<uint8_t>(SHPT_MULTIPOINTZ));
     647             :                 }
     648             :             }
     649             :             else
     650             :             {
     651           5 :                 if (bIsMeasured)
     652             :                 {
     653           1 :                     WriteUInt8(m_abyGeomBuffer,
     654             :                                static_cast<uint8_t>(SHPT_MULTIPOINTM));
     655             :                 }
     656             :                 else
     657             :                 {
     658           4 :                     WriteUInt8(m_abyGeomBuffer,
     659             :                                static_cast<uint8_t>(SHPT_MULTIPOINT));
     660             :                 }
     661             :             }
     662             : 
     663           9 :             const auto poMultiPoint = poGeom->toMultiPoint();
     664           9 :             const auto nNumGeoms = poMultiPoint->getNumGeometries();
     665           9 :             WriteVarUInt(m_abyGeomBuffer, nNumGeoms);
     666           9 :             if (nNumGeoms == 0)
     667           0 :                 return true;
     668             : 
     669           9 :             if (!EncodeEnvelope(m_abyGeomBuffer, poGeomField, poGeom))
     670           0 :                 return false;
     671             : 
     672             :             {
     673           9 :                 int64_t nLastX = 0;
     674           9 :                 int64_t nLastY = 0;
     675          26 :                 for (const auto *poPoint : *poMultiPoint)
     676             :                 {
     677          17 :                     const double dfX = poPoint->getX();
     678          17 :                     const double dfY = poPoint->getY();
     679             : 
     680             :                     double dfVal =
     681          17 :                         std::round((dfX - poGeomField->GetXOrigin()) *
     682          17 :                                    poGeomField->GetXYScale());
     683          17 :                     CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastX,
     684             :                                                    "Cannot encode value");
     685          17 :                     const int64_t nX = static_cast<int64_t>(dfVal);
     686          17 :                     WriteVarInt(m_abyGeomBuffer, nX - nLastX);
     687             : 
     688          17 :                     dfVal = std::round((dfY - poGeomField->GetYOrigin()) *
     689          17 :                                        poGeomField->GetXYScale());
     690          17 :                     CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastY,
     691             :                                                    "Cannot encode Y value");
     692          17 :                     const int64_t nY = static_cast<int64_t>(dfVal);
     693          17 :                     WriteVarInt(m_abyGeomBuffer, nY - nLastY);
     694             : 
     695          17 :                     nLastX = nX;
     696          17 :                     nLastY = nY;
     697             :                 }
     698             :             }
     699             : 
     700           9 :             if (bIs3D)
     701             :             {
     702           4 :                 int64_t nLastZ = 0;
     703          12 :                 for (const auto *poPoint : *poMultiPoint)
     704             :                 {
     705           8 :                     const double dfZ = poPoint->getZ();
     706             : 
     707             :                     double dfVal =
     708           8 :                         std::round((dfZ - poGeomField->GetZOrigin()) *
     709           8 :                                    poGeomField->GetZScale());
     710           8 :                     CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastZ,
     711             :                                                    "Bad Z value");
     712           8 :                     const int64_t nZ = static_cast<int64_t>(dfVal);
     713           8 :                     WriteVarInt(m_abyGeomBuffer, nZ - nLastZ);
     714             : 
     715           8 :                     nLastZ = nZ;
     716             :                 }
     717             :             }
     718             : 
     719           9 :             if (bIsMeasured)
     720             :             {
     721           2 :                 int64_t nLastM = 0;
     722           8 :                 for (const auto *poPoint : *poMultiPoint)
     723             :                 {
     724           6 :                     const double dfM = poPoint->getM();
     725             : 
     726             :                     double dfVal =
     727           6 :                         std::round((dfM - poGeomField->GetMOrigin()) *
     728           6 :                                    poGeomField->GetMScale());
     729           6 :                     CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastM,
     730             :                                                    "Bad M value");
     731           6 :                     const int64_t nM = static_cast<int64_t>(dfVal);
     732           6 :                     WriteVarInt(m_abyGeomBuffer, nM - nLastM);
     733             : 
     734           6 :                     nLastM = nM;
     735             :                 }
     736             :             }
     737             : 
     738           9 :             return true;
     739             :         }
     740             : 
     741          32 :         case wkbLineString:
     742             :         case wkbCircularString:
     743             :         case wkbCompoundCurve:
     744             :         case wkbMultiLineString:
     745             :         case wkbMultiCurve:
     746             :         {
     747          32 :             m_abyCurvePart.clear();
     748          32 :             m_anNumberPointsPerPart.clear();
     749          32 :             m_adfX.clear();
     750          32 :             m_adfY.clear();
     751          32 :             m_adfZ.clear();
     752          32 :             m_adfM.clear();
     753             : 
     754          32 :             int nCurveDescrCount = 0;
     755             :             const auto ProcessCurve =
     756          36 :                 [this, bIs3D, bIsMeasured, &nCurveDescrCount,
     757        4526 :                  &ReserveXYZMArrays](const OGRCurve *poCurve)
     758             :             {
     759          36 :                 if (auto poCC = dynamic_cast<const OGRCompoundCurve *>(poCurve))
     760             :                 {
     761           5 :                     const size_t nSizeBefore = m_adfX.size();
     762             : 
     763           5 :                     std::size_t nTotalSize = 0;
     764          12 :                     for (const auto *poSubCurve : *poCC)
     765             :                     {
     766           7 :                         nTotalSize += poSubCurve->getNumPoints();
     767             :                     }
     768           5 :                     ReserveXYZMArrays(nTotalSize);
     769             : 
     770           5 :                     bool bFirstSubCurve = true;
     771          12 :                     for (const auto *poSubCurve : *poCC)
     772             :                     {
     773           7 :                         if (const auto poLS =
     774           7 :                                 dynamic_cast<const OGRLineString *>(poSubCurve))
     775             :                         {
     776           4 :                             const int nNumPoints = poLS->getNumPoints();
     777          11 :                             for (int i = (bFirstSubCurve ? 0 : 1);
     778          11 :                                  i < nNumPoints; ++i)
     779             :                             {
     780           7 :                                 m_adfX.push_back(poLS->getX(i));
     781           7 :                                 m_adfY.push_back(poLS->getY(i));
     782           7 :                                 if (bIs3D)
     783           1 :                                     m_adfZ.push_back(poLS->getZ(i));
     784           7 :                                 if (bIsMeasured)
     785           1 :                                     m_adfM.push_back(poLS->getM(i));
     786             :                             }
     787             :                         }
     788           3 :                         else if (const auto poCS =
     789           0 :                                      dynamic_cast<const OGRCircularString *>(
     790           3 :                                          poSubCurve))
     791             :                         {
     792           3 :                             const int nNumPoints = poCS->getNumPoints();
     793           9 :                             for (int i = 0; i < nNumPoints; i++)
     794             :                             {
     795           6 :                                 if (i > 0 || bFirstSubCurve)
     796             :                                 {
     797           6 :                                     m_adfX.push_back(poCS->getX(i));
     798           6 :                                     m_adfY.push_back(poCS->getY(i));
     799           6 :                                     if (bIs3D)
     800           2 :                                         m_adfZ.push_back(poCS->getZ(i));
     801           6 :                                     if (bIsMeasured)
     802           2 :                                         m_adfM.push_back(poCS->getM(i));
     803             :                                 }
     804           6 :                                 if (i + 1 < nNumPoints)
     805             :                                 {
     806           3 :                                     ++nCurveDescrCount;
     807           3 :                                     ++i;
     808           3 :                                     WriteVarUInt(m_abyCurvePart,
     809             :                                                  static_cast<uint32_t>(
     810           3 :                                                      m_adfX.size() - 1));
     811           3 :                                     WriteUInt8(m_abyCurvePart,
     812             :                                                EXT_SHAPE_SEGMENT_ARC);
     813           3 :                                     WriteFloat64(m_abyCurvePart, poCS->getX(i));
     814           3 :                                     WriteFloat64(m_abyCurvePart, poCS->getY(i));
     815           3 :                                     WriteUInt32(m_abyCurvePart,
     816             :                                                 (1 << 7));  // DefinedIP
     817             :                                 }
     818             :                             }
     819             :                         }
     820             :                         else
     821             :                         {
     822           0 :                             CPLAssert(false);
     823             :                         }
     824           7 :                         bFirstSubCurve = false;
     825             :                     }
     826          10 :                     m_anNumberPointsPerPart.push_back(
     827           5 :                         static_cast<uint32_t>(m_adfX.size() - nSizeBefore));
     828             :                 }
     829          31 :                 else if (const auto poLS =
     830          31 :                              dynamic_cast<const OGRLineString *>(poCurve))
     831             :                 {
     832          28 :                     const int nNumPoints = poLS->getNumPoints();
     833          28 :                     m_anNumberPointsPerPart.push_back(nNumPoints);
     834          28 :                     ReserveXYZMArrays(nNumPoints);
     835        1099 :                     for (int i = 0; i < nNumPoints; ++i)
     836             :                     {
     837        1071 :                         m_adfX.push_back(poLS->getX(i));
     838        1071 :                         m_adfY.push_back(poLS->getY(i));
     839        1071 :                         if (bIs3D)
     840          24 :                             m_adfZ.push_back(poLS->getZ(i));
     841        1071 :                         if (bIsMeasured)
     842          16 :                             m_adfM.push_back(poLS->getM(i));
     843             :                     }
     844             :                 }
     845           3 :                 else if (const auto poCS =
     846           3 :                              dynamic_cast<const OGRCircularString *>(poCurve))
     847             :                 {
     848           3 :                     const int nNumPoints = poCS->getNumPoints();
     849           3 :                     const size_t nSizeBefore = m_adfX.size();
     850           3 :                     ReserveXYZMArrays(nNumPoints);
     851           9 :                     for (int i = 0; i < nNumPoints; i++)
     852             :                     {
     853           6 :                         m_adfX.push_back(poCS->getX(i));
     854           6 :                         m_adfY.push_back(poCS->getY(i));
     855           6 :                         if (bIs3D)
     856           2 :                             m_adfZ.push_back(poCS->getZ(i));
     857           6 :                         if (bIsMeasured)
     858           2 :                             m_adfM.push_back(poCS->getM(i));
     859           6 :                         if (i + 1 < nNumPoints)
     860             :                         {
     861           3 :                             ++nCurveDescrCount;
     862           3 :                             ++i;
     863           3 :                             WriteVarUInt(
     864           3 :                                 m_abyCurvePart,
     865           3 :                                 static_cast<uint32_t>(m_adfX.size() - 1));
     866           3 :                             WriteUInt8(m_abyCurvePart, EXT_SHAPE_SEGMENT_ARC);
     867           3 :                             WriteFloat64(m_abyCurvePart, poCS->getX(i));
     868           3 :                             WriteFloat64(m_abyCurvePart, poCS->getY(i));
     869           3 :                             WriteUInt32(m_abyCurvePart, (1 << 7));  // DefinedIP
     870             :                         }
     871             :                     }
     872           6 :                     m_anNumberPointsPerPart.push_back(
     873           3 :                         static_cast<uint32_t>(m_adfX.size() - nSizeBefore));
     874             :                 }
     875             :                 else
     876             :                 {
     877           0 :                     CPLAssert(false);
     878             :                 }
     879          36 :             };
     880             : 
     881          32 :             if (eFlatType == wkbMultiLineString || eFlatType == wkbMultiCurve)
     882             :             {
     883          15 :                 const auto poMultiCurve = poGeom->toMultiCurve();
     884          34 :                 for (const auto *poCurve : *poMultiCurve)
     885             :                 {
     886          19 :                     ProcessCurve(poCurve);
     887          15 :                 }
     888             :             }
     889             :             else
     890             :             {
     891          17 :                 ProcessCurve(poGeom->toCurve());
     892             :             }
     893             : 
     894          32 :             if (nCurveDescrCount > 0)
     895             :             {
     896           5 :                 WriteVarUInt(m_abyGeomBuffer,
     897           5 :                              SHPT_GENERALPOLYLINE | (1U << 29) |  // has curves
     898           5 :                                  ((bIsMeasured ? 1U : 0U) << 30) |
     899           5 :                                  ((bIs3D ? 1U : 0U) << 31));
     900             :             }
     901          27 :             else if (bIs3D)
     902             :             {
     903          10 :                 if (bIsMeasured)
     904             :                 {
     905           3 :                     WriteUInt8(m_abyGeomBuffer,
     906             :                                static_cast<uint8_t>(SHPT_ARCZM));
     907             :                 }
     908             :                 else
     909             :                 {
     910           7 :                     WriteUInt8(m_abyGeomBuffer,
     911             :                                static_cast<uint8_t>(SHPT_ARCZ));
     912             :                 }
     913             :             }
     914             :             else
     915             :             {
     916          17 :                 if (bIsMeasured)
     917             :                 {
     918           3 :                     WriteUInt8(m_abyGeomBuffer,
     919             :                                static_cast<uint8_t>(SHPT_ARCM));
     920             :                 }
     921             :                 else
     922             :                 {
     923          14 :                     WriteUInt8(m_abyGeomBuffer, static_cast<uint8_t>(SHPT_ARC));
     924             :                 }
     925             :             }
     926             : 
     927          32 :             return WriteEndOfCurveOrSurface(nCurveDescrCount);
     928             :         }
     929             : 
     930          51 :         case wkbPolygon:
     931             :         case wkbCurvePolygon:
     932             :         case wkbMultiPolygon:
     933             :         case wkbMultiSurface:
     934             :         {
     935          51 :             m_abyCurvePart.clear();
     936          51 :             m_anNumberPointsPerPart.clear();
     937          51 :             m_adfX.clear();
     938          51 :             m_adfY.clear();
     939          51 :             m_adfZ.clear();
     940          51 :             m_adfM.clear();
     941             : 
     942          51 :             int nCurveDescrCount = 0;
     943             :             const auto ProcessSurface =
     944          54 :                 [this, bIs3D, bIsMeasured, &nCurveDescrCount,
     945        2230 :                  &ReserveXYZMArrays](const OGRSurface *poSurface)
     946             :             {
     947          54 :                 if (const auto poPolygon =
     948          54 :                         dynamic_cast<const OGRPolygon *>(poSurface))
     949             :                 {
     950          43 :                     bool bFirstRing = true;
     951             : 
     952          43 :                     std::size_t nTotalSize = 0;
     953          92 :                     for (const auto *poLS : *poPolygon)
     954             :                     {
     955          49 :                         nTotalSize += poLS->getNumPoints();
     956             :                     }
     957          43 :                     ReserveXYZMArrays(nTotalSize);
     958             : 
     959          92 :                     for (const auto *poLS : *poPolygon)
     960             :                     {
     961          49 :                         const int nNumPoints = poLS->getNumPoints();
     962          49 :                         m_anNumberPointsPerPart.push_back(nNumPoints);
     963             :                         const bool bIsClockwise =
     964          49 :                             CPL_TO_BOOL(poLS->isClockwise());
     965          49 :                         const bool bReverseOrder =
     966          97 :                             (bFirstRing && !bIsClockwise) ||
     967          48 :                             (!bFirstRing && bIsClockwise);
     968          49 :                         bFirstRing = false;
     969         470 :                         for (int i = 0; i < nNumPoints; ++i)
     970             :                         {
     971         421 :                             const int j =
     972         421 :                                 bReverseOrder ? nNumPoints - 1 - i : i;
     973         421 :                             m_adfX.push_back(poLS->getX(j));
     974         421 :                             m_adfY.push_back(poLS->getY(j));
     975         421 :                             if (bIs3D)
     976          55 :                                 m_adfZ.push_back(poLS->getZ(j));
     977         421 :                             if (bIsMeasured)
     978          35 :                                 m_adfM.push_back(poLS->getM(j));
     979             :                         }
     980             :                     }
     981             :                 }
     982          11 :                 else if (const auto poCurvePoly =
     983          11 :                              dynamic_cast<const OGRCurvePolygon *>(poSurface))
     984             :                 {
     985          11 :                     bool bFirstRing = true;
     986          26 :                     for (const auto *poRing : *poCurvePoly)
     987             :                     {
     988             :                         const bool bIsClockwise =
     989          15 :                             CPL_TO_BOOL(poRing->isClockwise());
     990          15 :                         const bool bReverseOrder =
     991          27 :                             (bFirstRing && !bIsClockwise) ||
     992          12 :                             (!bFirstRing && bIsClockwise);
     993          15 :                         bFirstRing = false;
     994          15 :                         if (auto poCC =
     995          15 :                                 dynamic_cast<const OGRCompoundCurve *>(poRing))
     996             :                         {
     997           3 :                             const size_t nSizeBefore = m_adfX.size();
     998           3 :                             bool bFirstSubCurve = true;
     999           3 :                             const int nNumCurves = poCC->getNumCurves();
    1000          12 :                             for (int iSubCurve = 0; iSubCurve < nNumCurves;
    1001             :                                  ++iSubCurve)
    1002             :                             {
    1003          15 :                                 const OGRCurve *poSubCurve = poCC->getCurve(
    1004           6 :                                     bReverseOrder ? nNumCurves - 1 - iSubCurve
    1005             :                                                   : iSubCurve);
    1006           9 :                                 if (auto poLS =
    1007           0 :                                         dynamic_cast<const OGRLineString *>(
    1008           9 :                                             poSubCurve))
    1009             :                                 {
    1010           6 :                                     const int nNumPoints = poLS->getNumPoints();
    1011          18 :                                     for (int i = (bFirstSubCurve ? 0 : 1);
    1012          18 :                                          i < nNumPoints; ++i)
    1013             :                                     {
    1014          12 :                                         const int j = bReverseOrder
    1015          12 :                                                           ? nNumPoints - 1 - i
    1016             :                                                           : i;
    1017          12 :                                         m_adfX.push_back(poLS->getX(j));
    1018          12 :                                         m_adfY.push_back(poLS->getY(j));
    1019          12 :                                         if (bIs3D)
    1020           0 :                                             m_adfZ.push_back(poLS->getZ(j));
    1021          12 :                                         if (bIsMeasured)
    1022           0 :                                             m_adfM.push_back(poLS->getM(j));
    1023             :                                     }
    1024             :                                 }
    1025           0 :                                 else if (auto poCS = dynamic_cast<
    1026             :                                              const OGRCircularString *>(
    1027           3 :                                              poSubCurve))
    1028             :                                 {
    1029           3 :                                     const int nNumPoints = poCS->getNumPoints();
    1030           9 :                                     for (int i = 0; i < nNumPoints; i++)
    1031             :                                     {
    1032           6 :                                         if (i > 0 || bFirstSubCurve)
    1033             :                                         {
    1034           3 :                                             const int j =
    1035             :                                                 bReverseOrder
    1036           3 :                                                     ? nNumPoints - 1 - i
    1037             :                                                     : i;
    1038           3 :                                             m_adfX.push_back(poCS->getX(j));
    1039           3 :                                             m_adfY.push_back(poCS->getY(j));
    1040           3 :                                             if (bIs3D)
    1041           0 :                                                 m_adfZ.push_back(poCS->getZ(j));
    1042           3 :                                             if (bIsMeasured)
    1043           0 :                                                 m_adfM.push_back(poCS->getM(j));
    1044             :                                         }
    1045           6 :                                         if (i + 1 < nNumPoints)
    1046             :                                         {
    1047           3 :                                             ++nCurveDescrCount;
    1048           3 :                                             ++i;
    1049           3 :                                             const int j =
    1050             :                                                 bReverseOrder
    1051           3 :                                                     ? nNumPoints - 1 - i
    1052             :                                                     : i;
    1053           3 :                                             WriteVarUInt(
    1054           3 :                                                 m_abyCurvePart,
    1055             :                                                 static_cast<uint32_t>(
    1056           3 :                                                     m_adfX.size() - 1));
    1057           3 :                                             WriteUInt8(m_abyCurvePart,
    1058             :                                                        EXT_SHAPE_SEGMENT_ARC);
    1059           3 :                                             WriteFloat64(m_abyCurvePart,
    1060             :                                                          poCS->getX(j));
    1061           3 :                                             WriteFloat64(m_abyCurvePart,
    1062             :                                                          poCS->getY(j));
    1063           3 :                                             WriteUInt32(m_abyCurvePart,
    1064             :                                                         (1 << 7));  // DefinedIP
    1065             :                                         }
    1066             :                                     }
    1067             :                                 }
    1068             :                                 else
    1069             :                                 {
    1070           0 :                                     CPLAssert(false);
    1071             :                                 }
    1072           9 :                                 bFirstSubCurve = false;
    1073             :                             }
    1074           6 :                             m_anNumberPointsPerPart.push_back(
    1075           3 :                                 static_cast<uint32_t>(m_adfX.size() -
    1076             :                                                       nSizeBefore));
    1077             :                         }
    1078          12 :                         else if (const auto poLS =
    1079           0 :                                      dynamic_cast<const OGRLineString *>(
    1080          12 :                                          poRing))
    1081             :                         {
    1082           7 :                             const int nNumPoints = poLS->getNumPoints();
    1083           7 :                             m_anNumberPointsPerPart.push_back(nNumPoints);
    1084          38 :                             for (int i = 0; i < nNumPoints; ++i)
    1085             :                             {
    1086          31 :                                 const int j =
    1087          31 :                                     bReverseOrder ? nNumPoints - 1 - i : i;
    1088          31 :                                 m_adfX.push_back(poLS->getX(j));
    1089          31 :                                 m_adfY.push_back(poLS->getY(j));
    1090          31 :                                 if (bIs3D)
    1091           0 :                                     m_adfZ.push_back(poLS->getZ(j));
    1092          31 :                                 if (bIsMeasured)
    1093           0 :                                     m_adfM.push_back(poLS->getM(j));
    1094             :                             }
    1095             :                         }
    1096           5 :                         else if (const auto poCS =
    1097           0 :                                      dynamic_cast<const OGRCircularString *>(
    1098           5 :                                          poRing))
    1099             :                         {
    1100           5 :                             const int nNumPoints = poCS->getNumPoints();
    1101           5 :                             const size_t nSizeBefore = m_adfX.size();
    1102          20 :                             for (int i = 0; i < nNumPoints; i++)
    1103             :                             {
    1104          15 :                                 int j = bReverseOrder ? nNumPoints - 1 - i : i;
    1105          15 :                                 m_adfX.push_back(poCS->getX(j));
    1106          15 :                                 m_adfY.push_back(poCS->getY(j));
    1107          15 :                                 if (bIs3D)
    1108           3 :                                     m_adfZ.push_back(poCS->getZ(j));
    1109          15 :                                 if (bIsMeasured)
    1110           3 :                                     m_adfM.push_back(poCS->getM(j));
    1111          15 :                                 if (i + 1 < nNumPoints)
    1112             :                                 {
    1113          10 :                                     ++nCurveDescrCount;
    1114          10 :                                     ++i;
    1115          10 :                                     j = bReverseOrder ? nNumPoints - 1 - i : i;
    1116          10 :                                     WriteVarUInt(m_abyCurvePart,
    1117             :                                                  static_cast<uint32_t>(
    1118          10 :                                                      m_adfX.size() - 1));
    1119          10 :                                     WriteUInt8(m_abyCurvePart,
    1120             :                                                EXT_SHAPE_SEGMENT_ARC);
    1121          10 :                                     WriteFloat64(m_abyCurvePart, poCS->getX(j));
    1122          10 :                                     WriteFloat64(m_abyCurvePart, poCS->getY(j));
    1123          10 :                                     WriteUInt32(m_abyCurvePart,
    1124             :                                                 (1 << 7));  // DefinedIP
    1125             :                                 }
    1126             :                             }
    1127          10 :                             m_anNumberPointsPerPart.push_back(
    1128           5 :                                 static_cast<uint32_t>(m_adfX.size() -
    1129             :                                                       nSizeBefore));
    1130             :                         }
    1131             :                         else
    1132             :                         {
    1133           0 :                             CPLAssert(false);
    1134             :                         }
    1135             :                     }
    1136             :                 }
    1137             :                 else
    1138             :                 {
    1139           0 :                     CPLAssert(false);
    1140             :                 }
    1141          54 :             };
    1142             : 
    1143          51 :             if (eFlatType == wkbMultiPolygon || eFlatType == wkbMultiSurface)
    1144             :             {
    1145          18 :                 const auto poMultiSurface = poGeom->toMultiSurface();
    1146          39 :                 for (const auto *poSurface : *poMultiSurface)
    1147             :                 {
    1148          21 :                     ProcessSurface(poSurface);
    1149          18 :                 }
    1150             :             }
    1151             :             else
    1152             :             {
    1153          33 :                 ProcessSurface(poGeom->toSurface());
    1154             :             }
    1155             : 
    1156          51 :             if (nCurveDescrCount > 0)
    1157             :             {
    1158           7 :                 WriteVarUInt(m_abyGeomBuffer,
    1159           7 :                              SHPT_GENERALPOLYGON | (1U << 29) |  // has curves
    1160           7 :                                  ((bIsMeasured ? 1U : 0U) << 30) |
    1161           7 :                                  ((bIs3D ? 1U : 0U) << 31));
    1162             :             }
    1163          44 :             else if (bIs3D)
    1164             :             {
    1165          11 :                 if (bIsMeasured)
    1166             :                 {
    1167           4 :                     WriteUInt8(m_abyGeomBuffer,
    1168             :                                static_cast<uint8_t>(SHPT_POLYGONZM));
    1169             :                 }
    1170             :                 else
    1171             :                 {
    1172           7 :                     WriteUInt8(m_abyGeomBuffer,
    1173             :                                static_cast<uint8_t>(SHPT_POLYGONZ));
    1174             :                 }
    1175             :             }
    1176             :             else
    1177             :             {
    1178          33 :                 if (bIsMeasured)
    1179             :                 {
    1180           3 :                     WriteUInt8(m_abyGeomBuffer,
    1181             :                                static_cast<uint8_t>(SHPT_POLYGONM));
    1182             :                 }
    1183             :                 else
    1184             :                 {
    1185          30 :                     WriteUInt8(m_abyGeomBuffer,
    1186             :                                static_cast<uint8_t>(SHPT_POLYGON));
    1187             :                 }
    1188             :             }
    1189             : 
    1190          51 :             return WriteEndOfCurveOrSurface(nCurveDescrCount);
    1191             :         }
    1192             : 
    1193           3 :         case wkbTIN:
    1194             :         case wkbPolyhedralSurface:
    1195             :         case wkbGeometryCollection:
    1196             :         {
    1197           3 :             int nParts = 0;
    1198           6 :             std::vector<int> anPartStart;
    1199           6 :             std::vector<int> anPartType;
    1200           3 :             int nPoints = 0;
    1201           6 :             std::vector<OGRRawPoint> aoPoints;
    1202           6 :             std::vector<double> adfZ;
    1203             :             OGRErr eErr =
    1204           3 :                 OGRCreateMultiPatch(poGeom, TRUE, nParts, anPartStart,
    1205             :                                     anPartType, nPoints, aoPoints, adfZ);
    1206           3 :             if (eErr != OGRERR_NONE)
    1207           2 :                 return false;
    1208             : 
    1209           1 :             WriteUInt8(m_abyGeomBuffer, static_cast<uint8_t>(SHPT_MULTIPATCH));
    1210           1 :             WriteVarUInt(m_abyGeomBuffer, nPoints);
    1211           1 :             if (nPoints != 0)
    1212             :             {
    1213             :                 // Apparently we must write the size of the extended buffer
    1214             :                 // shape representation, even if we don't exactly follow this
    1215             :                 // format when writing to FileGDB files...
    1216           1 :                 int nShapeBufferSize =
    1217             :                     4;  // All types start with integer type number.
    1218           1 :                 nShapeBufferSize += 16 * 2;           // xy bbox.
    1219           1 :                 nShapeBufferSize += 4;                // nparts.
    1220           1 :                 nShapeBufferSize += 4;                // npoints.
    1221           1 :                 nShapeBufferSize += 4 * nParts;       // panPartStart[nparts].
    1222           1 :                 nShapeBufferSize += 4 * nParts;       // panPartType[nparts].
    1223           1 :                 nShapeBufferSize += 8 * 2 * nPoints;  // xy points.
    1224           1 :                 nShapeBufferSize += 16;               // z bbox.
    1225           1 :                 nShapeBufferSize += 8 * nPoints;      // z points.
    1226           1 :                 WriteVarUInt(m_abyGeomBuffer, nShapeBufferSize);
    1227             : 
    1228           1 :                 WriteVarUInt(m_abyGeomBuffer, nParts);
    1229             : 
    1230           1 :                 if (!EncodeEnvelope(m_abyGeomBuffer, poGeomField, poGeom))
    1231             :                 {
    1232           0 :                     return false;
    1233             :                 }
    1234             : 
    1235           2 :                 for (int i = 0; i < nParts - 1; i++)
    1236             :                 {
    1237           1 :                     WriteVarUInt(m_abyGeomBuffer,
    1238           1 :                                  anPartStart[i + 1] - anPartStart[i]);
    1239             :                 }
    1240             : 
    1241           3 :                 for (int i = 0; i < nParts; i++)
    1242             :                 {
    1243           2 :                     WriteVarUInt(m_abyGeomBuffer, anPartType[i]);
    1244             :                 }
    1245             : 
    1246             :                 {
    1247           1 :                     int64_t nLastX = 0;
    1248           1 :                     int64_t nLastY = 0;
    1249           8 :                     for (int i = 0; i < nPoints; ++i)
    1250             :                     {
    1251           7 :                         double dfVal = std::round(
    1252           7 :                             (aoPoints[i].x - poGeomField->GetXOrigin()) *
    1253           7 :                             poGeomField->GetXYScale());
    1254           7 :                         CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastX,
    1255             :                                                        "Cannot encode value");
    1256           7 :                         const int64_t nX = static_cast<int64_t>(dfVal);
    1257           7 :                         WriteVarInt(m_abyGeomBuffer, nX - nLastX);
    1258             : 
    1259           7 :                         dfVal = std::round(
    1260           7 :                             (aoPoints[i].y - poGeomField->GetYOrigin()) *
    1261           7 :                             poGeomField->GetXYScale());
    1262           7 :                         CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastY,
    1263             :                                                        "Cannot encode Y value");
    1264           7 :                         const int64_t nY = static_cast<int64_t>(dfVal);
    1265           7 :                         WriteVarInt(m_abyGeomBuffer, nY - nLastY);
    1266             : 
    1267           7 :                         nLastX = nX;
    1268           7 :                         nLastY = nY;
    1269             :                     }
    1270             :                 }
    1271             : 
    1272             :                 {
    1273           1 :                     int64_t nLastZ = 0;
    1274           8 :                     for (int i = 0; i < nPoints; ++i)
    1275             :                     {
    1276             :                         double dfVal =
    1277           7 :                             std::round((adfZ[i] - poGeomField->GetZOrigin()) *
    1278           7 :                                        poGeomField->GetZScale());
    1279           7 :                         CHECK_CAN_BE_ENCODED_ON_VARINT(dfVal, nLastZ,
    1280             :                                                        "Bad Z value");
    1281           7 :                         const int64_t nZ = static_cast<int64_t>(dfVal);
    1282           7 :                         WriteVarInt(m_abyGeomBuffer, nZ - nLastZ);
    1283             : 
    1284           7 :                         nLastZ = nZ;
    1285             :                     }
    1286             :                 }
    1287             :             }
    1288           1 :             return true;
    1289             :         }
    1290             : 
    1291           0 :         default:
    1292             :         {
    1293           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1294             :                      "Unsupported geometry type");
    1295           0 :             return false;
    1296             :         }
    1297             :     }
    1298             : }
    1299             : 
    1300             : /************************************************************************/
    1301             : /*                          EncodeFeature()                             */
    1302             : /************************************************************************/
    1303             : 
    1304       31505 : bool FileGDBTable::EncodeFeature(const std::vector<OGRField> &asRawFields,
    1305             :                                  const OGRGeometry *poGeom, int iSkipField)
    1306             : {
    1307       31505 :     m_abyBuffer.clear();
    1308       31505 :     if (iSkipField >= 0 && m_apoFields[iSkipField]->IsNullable())
    1309          22 :         m_abyBuffer.resize(BIT_ARRAY_SIZE_IN_BYTES(m_nCountNullableFields - 1),
    1310          22 :                            0xFF);
    1311             :     else
    1312       31483 :         m_abyBuffer.resize(m_nNullableFieldsSizeInBytes, 0xFF);
    1313             : 
    1314       31505 :     if (asRawFields.size() != m_apoFields.size())
    1315             :     {
    1316           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Bad size of asRawFields");
    1317           0 :         return false;
    1318             :     }
    1319       31505 :     int iNullableField = 0;
    1320      157856 :     for (int i = 0; i < static_cast<int>(m_apoFields.size()); ++i)
    1321             :     {
    1322      126355 :         if (i == iSkipField)
    1323          26 :             continue;
    1324      126329 :         auto &poField = m_apoFields[i];
    1325      126329 :         if (poField->GetType() == FGFT_OBJECTID)
    1326             :         {
    1327             :             // Implicit field
    1328       23628 :             continue;
    1329             :         }
    1330      102701 :         if (i == m_iGeomField)
    1331             :         {
    1332        4737 :             if (poGeom == nullptr)
    1333             :             {
    1334         858 :                 if (!poField->IsNullable())
    1335             :                 {
    1336           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1337             :                              "Attempting to write null geometry in "
    1338             :                              "non-nullable geometry field");
    1339           0 :                     return false;
    1340             :                 }
    1341         858 :                 iNullableField++;
    1342         858 :                 continue;
    1343             :             }
    1344             : 
    1345             :             auto poGeomField =
    1346        3879 :                 cpl::down_cast<FileGDBGeomField *>(poField.get());
    1347        3879 :             if (!EncodeGeometry(poGeomField, poGeom))
    1348           2 :                 return false;
    1349        3877 :             if (!poGeom->IsEmpty())
    1350             :             {
    1351        3865 :                 OGREnvelope3D oEnvelope;
    1352        3865 :                 poGeom->getEnvelope(&oEnvelope);
    1353        3865 :                 m_bDirtyGeomFieldBBox = true;
    1354        3865 :                 if (std::isnan(poGeomField->GetXMin()))
    1355             :                 {
    1356         115 :                     poGeomField->SetXYMinMax(oEnvelope.MinX, oEnvelope.MinY,
    1357             :                                              oEnvelope.MaxX, oEnvelope.MaxY);
    1358         115 :                     poGeomField->SetZMinMax(oEnvelope.MinZ, oEnvelope.MaxZ);
    1359             :                 }
    1360             :                 else
    1361             :                 {
    1362        3750 :                     poGeomField->SetXYMinMax(
    1363        3750 :                         std::min(poGeomField->GetXMin(), oEnvelope.MinX),
    1364        3750 :                         std::min(poGeomField->GetYMin(), oEnvelope.MinY),
    1365        3750 :                         std::max(poGeomField->GetXMax(), oEnvelope.MaxX),
    1366        3750 :                         std::max(poGeomField->GetYMax(), oEnvelope.MaxY));
    1367        3750 :                     poGeomField->SetZMinMax(
    1368        3750 :                         std::min(poGeomField->GetZMin(), oEnvelope.MinZ),
    1369        7500 :                         std::max(poGeomField->GetZMax(), oEnvelope.MaxZ));
    1370             :                 }
    1371             :             }
    1372             : 
    1373        3877 :             if (m_abyGeomBuffer.size() + m_abyBuffer.size() >
    1374             :                 static_cast<size_t>(INT_MAX))
    1375             :             {
    1376           0 :                 CPLError(CE_Failure, CPLE_AppDefined, "Too large feature");
    1377           0 :                 return false;
    1378             :             }
    1379             : 
    1380        3877 :             WriteVarUInt(m_abyBuffer, m_abyGeomBuffer.size());
    1381        3877 :             m_abyBuffer.insert(m_abyBuffer.end(), m_abyGeomBuffer.begin(),
    1382        7754 :                                m_abyGeomBuffer.end());
    1383             : 
    1384        3877 :             if (poField->IsNullable())
    1385             :             {
    1386        3877 :                 m_abyBuffer[iNullableField / 8] &= ~(1 << (iNullableField % 8));
    1387        3877 :                 iNullableField++;
    1388             :             }
    1389        3877 :             continue;
    1390             :         }
    1391             : 
    1392      195928 :         if (OGR_RawField_IsNull(&asRawFields[i]) ||
    1393       97964 :             OGR_RawField_IsUnset(&asRawFields[i]))
    1394             :         {
    1395        5326 :             if (!poField->IsNullable())
    1396             :             {
    1397           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1398             :                          "Attempting to write null/empty field in non-nullable "
    1399             :                          "field");
    1400           2 :                 return false;
    1401             :             }
    1402        5324 :             iNullableField++;
    1403        5324 :             continue;
    1404             :         }
    1405             : 
    1406       92638 :         switch (poField->GetType())
    1407             :         {
    1408           0 :             case FGFT_UNDEFINED:
    1409             :             {
    1410           0 :                 CPLAssert(false);
    1411             :                 break;
    1412             :             }
    1413             : 
    1414        3604 :             case FGFT_INT16:
    1415             :             {
    1416        3604 :                 WriteInt16(m_abyBuffer,
    1417        3604 :                            static_cast<int16_t>(asRawFields[i].Integer));
    1418        3604 :                 break;
    1419             :             }
    1420             : 
    1421        3733 :             case FGFT_INT32:
    1422             :             {
    1423        3733 :                 WriteInt32(m_abyBuffer, asRawFields[i].Integer);
    1424        3733 :                 break;
    1425             :             }
    1426             : 
    1427           4 :             case FGFT_FLOAT32:
    1428             :             {
    1429           4 :                 WriteFloat32(m_abyBuffer,
    1430           4 :                              static_cast<float>(asRawFields[i].Real));
    1431           4 :                 break;
    1432             :             }
    1433             : 
    1434        3994 :             case FGFT_FLOAT64:
    1435             :             {
    1436        3994 :                 WriteFloat64(m_abyBuffer, asRawFields[i].Real);
    1437        3994 :                 break;
    1438             :             }
    1439             : 
    1440       53296 :             case FGFT_STRING:
    1441             :             case FGFT_XML:
    1442             :             {
    1443       53296 :                 if (m_bStringsAreUTF8 || poField->GetType() == FGFT_XML)
    1444             :                 {
    1445       53295 :                     const auto nLen = strlen(asRawFields[i].String);
    1446       53295 :                     WriteVarUInt(m_abyBuffer, nLen);
    1447       53295 :                     if (nLen > 0)
    1448             :                     {
    1449       51846 :                         if (nLen + m_abyBuffer.size() >
    1450             :                             static_cast<size_t>(INT_MAX))
    1451             :                         {
    1452           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
    1453             :                                      "Too large feature");
    1454           0 :                             return false;
    1455             :                         }
    1456           0 :                         m_abyBuffer.insert(m_abyBuffer.end(),
    1457             :                                            reinterpret_cast<const uint8_t *>(
    1458       51846 :                                                asRawFields[i].String),
    1459             :                                            reinterpret_cast<const uint8_t *>(
    1460      103692 :                                                asRawFields[i].String) +
    1461      103692 :                                                nLen);
    1462             :                     }
    1463             :                 }
    1464             :                 else
    1465             :                 {
    1466           1 :                     WriteUTF16String(m_abyBuffer, asRawFields[i].String,
    1467             :                                      NUMBER_OF_BYTES_ON_VARUINT);
    1468             :                 }
    1469       53296 :                 break;
    1470             :             }
    1471             : 
    1472          66 :             case FGFT_DATETIME:
    1473             :             case FGFT_DATE:
    1474             :             {
    1475          66 :                 WriteFloat64(m_abyBuffer,
    1476             :                              FileGDBOGRDateToDoubleDate(
    1477          66 :                                  &asRawFields[i], /* bConvertToUTC = */ true,
    1478          66 :                                  poField->IsHighPrecision()));
    1479          66 :                 break;
    1480             :             }
    1481             : 
    1482           0 :             case FGFT_OBJECTID:
    1483             :             {
    1484           0 :                 CPLAssert(false);  // not possible given above processing
    1485             :                 break;
    1486             :             }
    1487             : 
    1488           0 :             case FGFT_GEOMETRY:
    1489             :             {
    1490           0 :                 CPLAssert(false);  // not possible given above processing
    1491             :                 break;
    1492             :             }
    1493             : 
    1494          19 :             case FGFT_BINARY:
    1495             :             {
    1496          19 :                 WriteVarUInt(m_abyBuffer, asRawFields[i].Binary.nCount);
    1497          19 :                 if (asRawFields[i].Binary.nCount)
    1498             :                 {
    1499          19 :                     if (static_cast<size_t>(asRawFields[i].Binary.nCount) +
    1500          19 :                             m_abyBuffer.size() >
    1501             :                         static_cast<size_t>(INT_MAX))
    1502             :                     {
    1503           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
    1504             :                                  "Too large feature");
    1505           0 :                         return false;
    1506             :                     }
    1507           0 :                     m_abyBuffer.insert(m_abyBuffer.end(),
    1508          19 :                                        asRawFields[i].Binary.paData,
    1509          38 :                                        asRawFields[i].Binary.paData +
    1510          38 :                                            asRawFields[i].Binary.nCount);
    1511             :                 }
    1512          19 :                 break;
    1513             :             }
    1514             : 
    1515           0 :             case FGFT_RASTER:
    1516             :             {
    1517             :                 // Not handled for now
    1518           0 :                 CPLAssert(false);
    1519             :                 break;
    1520             :             }
    1521             : 
    1522       27902 :             case FGFT_GUID:
    1523             :             case FGFT_GLOBALID:
    1524             :             {
    1525       27902 :                 const auto nLen = strlen(asRawFields[i].String);
    1526       27902 :                 if (nLen != 38)
    1527             :                 {
    1528           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    1529             :                              "Bad size for UUID field");
    1530           0 :                     return false;
    1531             :                 }
    1532       55804 :                 std::vector<unsigned> anVals(16);
    1533       27902 :                 sscanf(asRawFields[i].String,
    1534             :                        "{%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%"
    1535             :                        "02X%02X%02X%02X}",
    1536       27902 :                        &anVals[3], &anVals[2], &anVals[1], &anVals[0],
    1537       27902 :                        &anVals[5], &anVals[4], &anVals[7], &anVals[6],
    1538       27902 :                        &anVals[8], &anVals[9], &anVals[10], &anVals[11],
    1539       27902 :                        &anVals[12], &anVals[13], &anVals[14], &anVals[15]);
    1540      474334 :                 for (auto v : anVals)
    1541             :                 {
    1542      446432 :                     m_abyBuffer.push_back(static_cast<uint8_t>(v));
    1543             :                 }
    1544       27902 :                 break;
    1545             :             }
    1546             : 
    1547           2 :             case FGFT_INT64:
    1548             :             {
    1549           2 :                 WriteInt64(m_abyBuffer, asRawFields[i].Integer64);
    1550           2 :                 break;
    1551             :             }
    1552             : 
    1553           6 :             case FGFT_TIME:
    1554             :             {
    1555           6 :                 WriteFloat64(m_abyBuffer,
    1556           6 :                              FileGDBOGRTimeToDoubleTime(&asRawFields[i]));
    1557           6 :                 break;
    1558             :             }
    1559             : 
    1560          12 :             case FGFT_DATETIME_WITH_OFFSET:
    1561             :             {
    1562          12 :                 WriteFloat64(m_abyBuffer, FileGDBOGRDateToDoubleDate(
    1563          12 :                                               &asRawFields[i],
    1564             :                                               /* bConvertToUTC = */ false,
    1565             :                                               /* bIsHighPrecision = */ true));
    1566          12 :                 if (asRawFields[i].Date.TZFlag > 1)
    1567             :                 {
    1568          12 :                     WriteInt16(m_abyBuffer,
    1569             :                                static_cast<int16_t>(
    1570          12 :                                    (asRawFields[i].Date.TZFlag - 100) * 15));
    1571             :                 }
    1572             :                 else
    1573             :                 {
    1574           0 :                     WriteInt16(m_abyBuffer, 0);
    1575             :                 }
    1576          12 :                 break;
    1577             :             }
    1578             :         }
    1579             : 
    1580       92638 :         if (poField->IsNullable())
    1581             :         {
    1582       37245 :             m_abyBuffer[iNullableField / 8] &= ~(1 << (iNullableField % 8));
    1583       37245 :             iNullableField++;
    1584             :         }
    1585             :     }
    1586             : 
    1587       31501 :     if (m_abyBuffer.size() > static_cast<size_t>(INT_MAX))
    1588             :     {
    1589           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Too large feature");
    1590           0 :         return false;
    1591             :     }
    1592             : 
    1593       31501 :     return true;
    1594             : }
    1595             : 
    1596             : /************************************************************************/
    1597             : /*                       SeekIntoTableXForNewFeature()                  */
    1598             : /************************************************************************/
    1599             : 
    1600       31424 : bool FileGDBTable::SeekIntoTableXForNewFeature(int nObjectID)
    1601             : {
    1602             :     int iCorrectedRow;
    1603       31424 :     bool bWriteEmptyPageAtEnd = false;
    1604       31424 :     const uint32_t nPageSize = TABLX_FEATURES_PER_PAGE * m_nTablxOffsetSize;
    1605             : 
    1606       31424 :     if (m_abyTablXBlockMap.empty())
    1607             :     {
    1608             :         // Is the OID to write in the current allocated pages, or in the next
    1609             :         // page ?
    1610       31394 :         if ((nObjectID - 1) / TABLX_FEATURES_PER_PAGE <=
    1611       31394 :             ((m_nTotalRecordCount == 0)
    1612       31394 :                  ? 0
    1613       29619 :                  : (1 + (m_nTotalRecordCount - 1) / TABLX_FEATURES_PER_PAGE)))
    1614             :         {
    1615       31372 :             iCorrectedRow = nObjectID - 1;
    1616       31372 :             const auto n1024BlocksPresentBefore = m_n1024BlocksPresent;
    1617       31372 :             m_n1024BlocksPresent =
    1618       31372 :                 DIV_ROUND_UP(std::max(m_nTotalRecordCount, nObjectID),
    1619             :                              TABLX_FEATURES_PER_PAGE);
    1620       31372 :             bWriteEmptyPageAtEnd =
    1621       31372 :                 m_n1024BlocksPresent > n1024BlocksPresentBefore;
    1622             :         }
    1623             :         else
    1624             :         {
    1625             :             // No, then we have a sparse table, and need to use a bitmap
    1626          44 :             m_abyTablXBlockMap.resize(
    1627          22 :                 (DIV_ROUND_UP(nObjectID, TABLX_FEATURES_PER_PAGE) + 7) / 8);
    1628          22 :             for (int i = 0;
    1629          26 :                  i < DIV_ROUND_UP(m_nTotalRecordCount, TABLX_FEATURES_PER_PAGE);
    1630             :                  ++i)
    1631           4 :                 m_abyTablXBlockMap[i / 8] |= (1 << (i % 8));
    1632          22 :             const int iBlock = (nObjectID - 1) / TABLX_FEATURES_PER_PAGE;
    1633          22 :             m_abyTablXBlockMap[iBlock / 8] |= (1 << (iBlock % 8));
    1634          22 :             iCorrectedRow =
    1635          22 :                 DIV_ROUND_UP(m_nTotalRecordCount, TABLX_FEATURES_PER_PAGE) *
    1636             :                     TABLX_FEATURES_PER_PAGE +
    1637          22 :                 ((nObjectID - 1) % TABLX_FEATURES_PER_PAGE);
    1638          22 :             m_n1024BlocksPresent++;
    1639          22 :             bWriteEmptyPageAtEnd = true;
    1640             :         }
    1641             :     }
    1642             :     else
    1643             :     {
    1644          30 :         const int iBlock = (nObjectID - 1) / TABLX_FEATURES_PER_PAGE;
    1645             : 
    1646          30 :         if (nObjectID <= m_nTotalRecordCount)
    1647             :         {
    1648          16 :             CPLAssert(iBlock / 8 < static_cast<int>(m_abyTablXBlockMap.size()));
    1649          16 :             if (TEST_BIT(m_abyTablXBlockMap.data(), iBlock) == 0)
    1650             :             {
    1651             :                 // This requires rewriting the gdbtablx file to insert
    1652             :                 // a new page
    1653          10 :                 GUInt32 nCountBlocksBefore = 0;
    1654          16 :                 for (int i = 0; i < iBlock; i++)
    1655           6 :                     nCountBlocksBefore +=
    1656           6 :                         TEST_BIT(m_abyTablXBlockMap.data(), i) != 0;
    1657             : 
    1658          10 :                 std::vector<GByte> abyTmp(nPageSize);
    1659          10 :                 uint64_t nOffset =
    1660             :                     TABLX_HEADER_SIZE +
    1661          10 :                     static_cast<uint64_t>(m_n1024BlocksPresent) * nPageSize;
    1662          22 :                 for (int i = m_n1024BlocksPresent - 1;
    1663          22 :                      i >= static_cast<int>(nCountBlocksBefore); --i)
    1664             :                 {
    1665          12 :                     nOffset -= nPageSize;
    1666          12 :                     VSIFSeekL(m_fpTableX, nOffset, SEEK_SET);
    1667          12 :                     if (VSIFReadL(abyTmp.data(), nPageSize, 1, m_fpTableX) != 1)
    1668             :                     {
    1669           0 :                         CPLError(CE_Failure, CPLE_FileIO,
    1670             :                                  "Cannot read .gdtablx page at offset %u",
    1671             :                                  static_cast<uint32_t>(nOffset));
    1672           0 :                         return false;
    1673             :                     }
    1674          12 :                     VSIFSeekL(m_fpTableX, VSIFTellL(m_fpTableX), SEEK_SET);
    1675          12 :                     if (VSIFWriteL(abyTmp.data(), nPageSize, 1, m_fpTableX) !=
    1676             :                         1)
    1677             :                     {
    1678           0 :                         CPLError(CE_Failure, CPLE_FileIO,
    1679             :                                  "Cannot rewrite .gdtablx page of offset %u",
    1680             :                                  static_cast<uint32_t>(nOffset));
    1681           0 :                         return false;
    1682             :                     }
    1683             :                 }
    1684          10 :                 abyTmp.clear();
    1685          10 :                 abyTmp.resize(nPageSize);
    1686          10 :                 nOffset = TABLX_HEADER_SIZE +
    1687          10 :                           static_cast<uint64_t>(nCountBlocksBefore) * nPageSize;
    1688          10 :                 VSIFSeekL(m_fpTableX, nOffset, SEEK_SET);
    1689          10 :                 if (VSIFWriteL(abyTmp.data(), nPageSize, 1, m_fpTableX) != 1)
    1690             :                 {
    1691           0 :                     CPLError(CE_Failure, CPLE_FileIO,
    1692             :                              "Cannot write empty .gdtablx page of offset %u",
    1693             :                              static_cast<uint32_t>(nOffset));
    1694           0 :                     return false;
    1695             :                 }
    1696          10 :                 m_abyTablXBlockMap[iBlock / 8] |= (1 << (iBlock % 8));
    1697          10 :                 m_n1024BlocksPresent++;
    1698          10 :                 m_bDirtyTableXTrailer = true;
    1699          10 :                 m_nOffsetTableXTrailer = 0;
    1700          10 :                 m_nCountBlocksBeforeIBlockIdx = iBlock;
    1701          10 :                 m_nCountBlocksBeforeIBlockValue = nCountBlocksBefore;
    1702             :             }
    1703             :         }
    1704          28 :         else if (DIV_ROUND_UP(nObjectID, TABLX_FEATURES_PER_PAGE) >
    1705          14 :                  DIV_ROUND_UP(m_nTotalRecordCount, TABLX_FEATURES_PER_PAGE))
    1706             :         {
    1707          16 :             m_abyTablXBlockMap.resize(
    1708           8 :                 (DIV_ROUND_UP(nObjectID, TABLX_FEATURES_PER_PAGE) + 7) / 8);
    1709           8 :             m_abyTablXBlockMap[iBlock / 8] |= (1 << (iBlock % 8));
    1710           8 :             m_n1024BlocksPresent++;
    1711           8 :             bWriteEmptyPageAtEnd = true;
    1712             :         }
    1713             : 
    1714          30 :         GUInt32 nCountBlocksBefore = 0;
    1715             :         // In case of sequential access, optimization to avoid recomputing
    1716             :         // the number of blocks since the beginning of the map
    1717          30 :         if (iBlock >= m_nCountBlocksBeforeIBlockIdx)
    1718             :         {
    1719          30 :             nCountBlocksBefore = m_nCountBlocksBeforeIBlockValue;
    1720          70 :             for (int i = m_nCountBlocksBeforeIBlockIdx; i < iBlock; i++)
    1721          40 :                 nCountBlocksBefore +=
    1722          40 :                     TEST_BIT(m_abyTablXBlockMap.data(), i) != 0;
    1723             :         }
    1724             :         else
    1725             :         {
    1726           0 :             nCountBlocksBefore = 0;
    1727           0 :             for (int i = 0; i < iBlock; i++)
    1728           0 :                 nCountBlocksBefore +=
    1729           0 :                     TEST_BIT(m_abyTablXBlockMap.data(), i) != 0;
    1730             :         }
    1731             : 
    1732          30 :         m_nCountBlocksBeforeIBlockIdx = iBlock;
    1733          30 :         m_nCountBlocksBeforeIBlockValue = nCountBlocksBefore;
    1734          30 :         iCorrectedRow = nCountBlocksBefore * TABLX_FEATURES_PER_PAGE +
    1735          30 :                         ((nObjectID - 1) % TABLX_FEATURES_PER_PAGE);
    1736             :     }
    1737             : 
    1738       31424 :     if (bWriteEmptyPageAtEnd)
    1739             :     {
    1740        1805 :         m_bDirtyTableXTrailer = true;
    1741        1805 :         m_nOffsetTableXTrailer = 0;
    1742        1805 :         std::vector<GByte> abyTmp(nPageSize);
    1743        1805 :         uint64_t nOffset =
    1744             :             TABLX_HEADER_SIZE +
    1745        1805 :             static_cast<uint64_t>(m_n1024BlocksPresent - 1) * nPageSize;
    1746        1805 :         VSIFSeekL(m_fpTableX, nOffset, SEEK_SET);
    1747        1805 :         if (VSIFWriteL(abyTmp.data(), nPageSize, 1, m_fpTableX) != 1)
    1748             :         {
    1749           0 :             CPLError(CE_Failure, CPLE_FileIO,
    1750             :                      "Cannot write empty .gdtablx page of offset %u",
    1751             :                      static_cast<uint32_t>(nOffset));
    1752           0 :             return false;
    1753             :         }
    1754             :     }
    1755             : 
    1756       31424 :     const uint64_t nOffset =
    1757             :         TABLX_HEADER_SIZE +
    1758       31424 :         static_cast<uint64_t>(iCorrectedRow) * m_nTablxOffsetSize;
    1759       31424 :     VSIFSeekL(m_fpTableX, nOffset, SEEK_SET);
    1760             : 
    1761       31424 :     return true;
    1762             : }
    1763             : 
    1764             : /************************************************************************/
    1765             : /*                        WriteFeatureOffset()                          */
    1766             : /************************************************************************/
    1767             : 
    1768        2746 : void FileGDBTable::WriteFeatureOffset(uint64_t nFeatureOffset,
    1769             :                                       GByte *pabyBuffer)
    1770             : {
    1771        2746 :     CPL_LSBPTR64(&nFeatureOffset);
    1772        2746 :     memcpy(pabyBuffer, &nFeatureOffset, m_nTablxOffsetSize);
    1773        2746 : }
    1774             : 
    1775             : /************************************************************************/
    1776             : /*                        WriteFeatureOffset()                          */
    1777             : /************************************************************************/
    1778             : 
    1779       34094 : bool FileGDBTable::WriteFeatureOffset(uint64_t nFeatureOffset)
    1780             : {
    1781       34094 :     CPL_LSBPTR64(&nFeatureOffset);
    1782       34094 :     return VSIFWriteL(&nFeatureOffset, m_nTablxOffsetSize, 1, m_fpTableX) == 1;
    1783             : }
    1784             : 
    1785             : /************************************************************************/
    1786             : /*                          CreateFeature()                             */
    1787             : /************************************************************************/
    1788             : 
    1789       31430 : bool FileGDBTable::CreateFeature(const std::vector<OGRField> &asRawFields,
    1790             :                                  const OGRGeometry *poGeom, int *pnFID)
    1791             : {
    1792       31430 :     if (!m_bUpdate)
    1793           0 :         return false;
    1794             : 
    1795       31430 :     if (m_bDirtyFieldDescriptors && !WriteFieldDescriptors(m_fpTable))
    1796           0 :         return false;
    1797             : 
    1798             :     int nObjectID;
    1799       31430 :     if (pnFID != nullptr && *pnFID > 0)
    1800             :     {
    1801         126 :         if (*pnFID <= m_nTotalRecordCount &&
    1802          22 :             GetOffsetInTableForRow((*pnFID) - 1) != 0)
    1803             :         {
    1804           2 :             CPLError(
    1805             :                 CE_Failure, CPLE_AppDefined,
    1806             :                 "Cannot create feature of ID %d because one already exists",
    1807             :                 *pnFID);
    1808           2 :             return false;
    1809             :         }
    1810         102 :         nObjectID = *pnFID;
    1811             :     }
    1812             :     else
    1813             :     {
    1814       31326 :         if (m_nTotalRecordCount == std::numeric_limits<int>::max())
    1815             :         {
    1816           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1817             :                      "Maximum number of records per table reached");
    1818           0 :             return false;
    1819             :         }
    1820       31326 :         nObjectID = m_nTotalRecordCount + 1;
    1821             :     }
    1822             : 
    1823             :     try
    1824             :     {
    1825       31428 :         if (!EncodeFeature(asRawFields, poGeom, -1))
    1826           4 :             return false;
    1827             :     }
    1828           0 :     catch (const std::exception &e)
    1829             :     {
    1830           0 :         CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
    1831           0 :         return false;
    1832             :     }
    1833             : 
    1834       31424 :     const uint64_t nFreeOffset = GetOffsetOfFreeAreaFromFreeList(
    1835       31424 :         static_cast<uint32_t>(sizeof(uint32_t) + m_abyBuffer.size()));
    1836       31424 :     if (nFreeOffset == OFFSET_MINUS_ONE)
    1837             :     {
    1838       28809 :         if (((m_nFileSize + m_abyBuffer.size()) >> (8 * m_nTablxOffsetSize)) !=
    1839             :             0)
    1840             :         {
    1841           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1842             :                      "Maximum file size for m_nTablxOffsetSize = %u reached",
    1843             :                      m_nTablxOffsetSize);
    1844           0 :             return false;
    1845             :         }
    1846             :     }
    1847             : 
    1848       31424 :     if (!SeekIntoTableXForNewFeature(nObjectID))
    1849           0 :         return false;
    1850             : 
    1851       31424 :     if (nFreeOffset == OFFSET_MINUS_ONE)
    1852             :     {
    1853       28809 :         VSIFSeekL(m_fpTable, m_nFileSize, SEEK_SET);
    1854             :     }
    1855             :     else
    1856             :     {
    1857        2615 :         VSIFSeekL(m_fpTable, nFreeOffset, SEEK_SET);
    1858             :     }
    1859       31424 :     if (!WriteUInt32(m_fpTable, static_cast<uint32_t>(m_abyBuffer.size())))
    1860           0 :         return false;
    1861       62845 :     if (!m_abyBuffer.empty() &&
    1862       31421 :         VSIFWriteL(m_abyBuffer.data(), 1, m_abyBuffer.size(), m_fpTable) !=
    1863       31421 :             m_abyBuffer.size())
    1864             :     {
    1865           0 :         return false;
    1866             :     }
    1867             : 
    1868       31424 :     if (!WriteFeatureOffset(nFreeOffset == OFFSET_MINUS_ONE ? m_nFileSize
    1869             :                                                             : nFreeOffset))
    1870           0 :         return false;
    1871       31424 :     if (pnFID)
    1872        9273 :         *pnFID = nObjectID;
    1873             : 
    1874       31424 :     m_nRowBlobLength = static_cast<uint32_t>(m_abyBuffer.size());
    1875       31424 :     if (m_nRowBlobLength > m_nHeaderBufferMaxSize)
    1876             :     {
    1877        5210 :         m_nHeaderBufferMaxSize = m_nRowBlobLength;
    1878             :     }
    1879       31424 :     m_nRowBufferMaxSize = std::max(m_nRowBufferMaxSize, m_nRowBlobLength);
    1880       31424 :     if (nFreeOffset == OFFSET_MINUS_ONE)
    1881             :     {
    1882       28809 :         m_nFileSize += sizeof(uint32_t) + m_nRowBlobLength;
    1883             :     }
    1884             : 
    1885       31424 :     m_nTotalRecordCount = std::max(m_nTotalRecordCount, nObjectID);
    1886       31424 :     m_nValidRecordCount++;
    1887             : 
    1888       31424 :     m_bDirtyHeader = true;
    1889       31424 :     m_bDirtyTableXHeader = true;
    1890             : 
    1891       31424 :     m_bDirtyIndices = true;
    1892             : 
    1893       31424 :     return true;
    1894             : }
    1895             : 
    1896             : /************************************************************************/
    1897             : /*                          UpdateFeature()                             */
    1898             : /************************************************************************/
    1899             : 
    1900          51 : bool FileGDBTable::UpdateFeature(int nFID,
    1901             :                                  const std::vector<OGRField> &asRawFields,
    1902             :                                  const OGRGeometry *poGeom)
    1903             : {
    1904          51 :     if (!m_bUpdate)
    1905           0 :         return false;
    1906             : 
    1907          51 :     if (m_bDirtyFieldDescriptors && !WriteFieldDescriptors(m_fpTable))
    1908           0 :         return false;
    1909             : 
    1910          51 :     vsi_l_offset nOffsetInTableX = 0;
    1911             :     vsi_l_offset nOffsetInTable =
    1912          51 :         GetOffsetInTableForRow(nFID - 1, &nOffsetInTableX);
    1913          51 :     if (nOffsetInTable == 0)
    1914           0 :         return false;
    1915             : 
    1916             :     try
    1917             :     {
    1918          51 :         if (!EncodeFeature(asRawFields, poGeom, -1))
    1919           0 :             return false;
    1920             :     }
    1921           0 :     catch (const std::exception &e)
    1922             :     {
    1923           0 :         CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
    1924           0 :         return false;
    1925             :     }
    1926             : 
    1927          51 :     VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
    1928          51 :     uint32_t nOldFeatureSize = 0;
    1929          51 :     if (!ReadUInt32(m_fpTable, nOldFeatureSize))
    1930           0 :         return false;
    1931             : 
    1932          51 :     m_nCurRow = -1;
    1933             : 
    1934          51 :     if (m_abyBuffer.size() <= nOldFeatureSize)
    1935             :     {
    1936             :         // Can rewrite-in-place
    1937          27 :         VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
    1938             : 
    1939          27 :         if (!WriteUInt32(m_fpTable, static_cast<uint32_t>(m_abyBuffer.size())))
    1940           0 :             return false;
    1941          54 :         if (!m_abyBuffer.empty() &&
    1942          27 :             VSIFWriteL(m_abyBuffer.data(), 1, m_abyBuffer.size(), m_fpTable) !=
    1943          27 :                 m_abyBuffer.size())
    1944             :         {
    1945           0 :             return false;
    1946             :         }
    1947             : 
    1948          27 :         m_nRowBlobLength = 0;
    1949          27 :         const size_t nSizeToBlank = nOldFeatureSize - m_abyBuffer.size();
    1950          27 :         if (nSizeToBlank > 0)
    1951             :         {
    1952             :             // Blank unused areas of the old feature
    1953          14 :             m_abyBuffer.clear();
    1954             :             try
    1955             :             {
    1956          14 :                 m_abyBuffer.resize(nSizeToBlank);
    1957          14 :                 CPL_IGNORE_RET_VAL(VSIFWriteL(m_abyBuffer.data(), 1,
    1958             :                                               m_abyBuffer.size(), m_fpTable));
    1959             :             }
    1960           0 :             catch (const std::exception &e)
    1961             :             {
    1962           0 :                 CPLDebug("OpenFileGDB",
    1963             :                          "Could not blank no longer part of feature: %s",
    1964           0 :                          e.what());
    1965             :             }
    1966             :         }
    1967             :     }
    1968             :     else
    1969             :     {
    1970             :         // Updated feature is larger than older one: check if there's a chunk
    1971             :         // we can reuse by examining the .freelist, and if not, append at end
    1972             :         // of .gdbtable
    1973          24 :         const uint64_t nFreeOffset = GetOffsetOfFreeAreaFromFreeList(
    1974          24 :             static_cast<uint32_t>(sizeof(uint32_t) + m_abyBuffer.size()));
    1975             : 
    1976          24 :         if (nFreeOffset == OFFSET_MINUS_ONE)
    1977             :         {
    1978          24 :             if (((m_nFileSize + m_abyBuffer.size()) >>
    1979          24 :                  (8 * m_nTablxOffsetSize)) != 0)
    1980             :             {
    1981           0 :                 CPLError(
    1982             :                     CE_Failure, CPLE_AppDefined,
    1983             :                     "Maximum file size for m_nTablxOffsetSize = %u reached",
    1984             :                     m_nTablxOffsetSize);
    1985           0 :                 return false;
    1986             :             }
    1987             : 
    1988          24 :             VSIFSeekL(m_fpTable, m_nFileSize, SEEK_SET);
    1989             :         }
    1990             :         else
    1991             :         {
    1992           0 :             VSIFSeekL(m_fpTable, nFreeOffset, SEEK_SET);
    1993             :         }
    1994             : 
    1995          24 :         if (!WriteUInt32(m_fpTable, static_cast<uint32_t>(m_abyBuffer.size())))
    1996           0 :             return false;
    1997          48 :         if (!m_abyBuffer.empty() &&
    1998          24 :             VSIFWriteL(m_abyBuffer.data(), 1, m_abyBuffer.size(), m_fpTable) !=
    1999          24 :                 m_abyBuffer.size())
    2000             :         {
    2001           0 :             return false;
    2002             :         }
    2003             : 
    2004             :         // Update offset of feature in .gdbtablx
    2005          24 :         VSIFSeekL(m_fpTableX, nOffsetInTableX, SEEK_SET);
    2006          24 :         if (!WriteFeatureOffset(nFreeOffset == OFFSET_MINUS_ONE ? m_nFileSize
    2007             :                                                                 : nFreeOffset))
    2008           0 :             return false;
    2009             : 
    2010          24 :         m_nRowBlobLength = static_cast<uint32_t>(m_abyBuffer.size());
    2011          24 :         if (m_nRowBlobLength > m_nHeaderBufferMaxSize)
    2012             :         {
    2013          18 :             m_bDirtyHeader = true;
    2014          18 :             m_nHeaderBufferMaxSize = m_nRowBlobLength;
    2015             :         }
    2016          24 :         m_nRowBufferMaxSize = std::max(m_nRowBufferMaxSize, m_nRowBlobLength);
    2017          24 :         if (nFreeOffset == OFFSET_MINUS_ONE)
    2018             :         {
    2019          24 :             m_nFileSize += sizeof(uint32_t) + m_nRowBlobLength;
    2020             :         }
    2021             : 
    2022          24 :         AddEntryToFreelist(nOffsetInTable, sizeof(uint32_t) + nOldFeatureSize);
    2023             : 
    2024             :         // Blank previously used area
    2025          24 :         VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
    2026          24 :         const uint32_t nNegatedOldFeatureSize =
    2027          24 :             static_cast<uint32_t>(-static_cast<int>(nOldFeatureSize));
    2028          24 :         if (!WriteUInt32(m_fpTable, nNegatedOldFeatureSize))
    2029           0 :             return false;
    2030          24 :         m_abyBuffer.clear();
    2031             :         try
    2032             :         {
    2033          24 :             m_abyBuffer.resize(nOldFeatureSize);
    2034          24 :             CPL_IGNORE_RET_VAL(VSIFWriteL(m_abyBuffer.data(), 1,
    2035             :                                           m_abyBuffer.size(), m_fpTable));
    2036             :         }
    2037           0 :         catch (const std::exception &e)
    2038             :         {
    2039           0 :             CPLDebug("OpenFileGDB", "Could not blank old feature: %s",
    2040           0 :                      e.what());
    2041             :         }
    2042             :     }
    2043             : 
    2044          51 :     m_bDirtyIndices = true;
    2045             : 
    2046          51 :     return true;
    2047             : }
    2048             : 
    2049             : /************************************************************************/
    2050             : /*                          DeleteFeature()                             */
    2051             : /************************************************************************/
    2052             : 
    2053        2646 : bool FileGDBTable::DeleteFeature(int nFID)
    2054             : {
    2055        2646 :     if (!m_bUpdate)
    2056           0 :         return false;
    2057             : 
    2058        2646 :     if (m_bDirtyFieldDescriptors && !WriteFieldDescriptors(m_fpTable))
    2059           0 :         return false;
    2060             : 
    2061        2646 :     vsi_l_offset nOffsetInTableX = 0;
    2062             :     vsi_l_offset nOffsetInTable =
    2063        2646 :         GetOffsetInTableForRow(nFID - 1, &nOffsetInTableX);
    2064        2646 :     if (nOffsetInTable == 0)
    2065           0 :         return false;
    2066             : 
    2067             :     // Set 0 as offset for the feature in .gdbtablx
    2068        2646 :     VSIFSeekL(m_fpTableX, nOffsetInTableX, SEEK_SET);
    2069        2646 :     if (!WriteFeatureOffset(0))
    2070           0 :         return false;
    2071             : 
    2072             :     // Negate the size of the feature in .gdbtable
    2073        2646 :     VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
    2074        2646 :     uint32_t nFeatureSize = 0;
    2075        2646 :     if (!ReadUInt32(m_fpTable, nFeatureSize))
    2076           0 :         return false;
    2077        2646 :     if (nFeatureSize > static_cast<uint32_t>(INT_MAX))
    2078           0 :         return false;
    2079        2646 :     const int nDeletedFeatureSize =
    2080        2646 :         static_cast<uint32_t>(-static_cast<int32_t>(nFeatureSize));
    2081        2646 :     VSIFSeekL(m_fpTable, nOffsetInTable, SEEK_SET);
    2082        2646 :     if (!WriteUInt32(m_fpTable, nDeletedFeatureSize))
    2083           0 :         return false;
    2084             : 
    2085        2646 :     AddEntryToFreelist(nOffsetInTable, sizeof(uint32_t) + nFeatureSize);
    2086             : 
    2087             :     // Blank feature content
    2088        2646 :     m_nCurRow = -1;
    2089        2646 :     m_abyBuffer.clear();
    2090             :     try
    2091             :     {
    2092        2646 :         m_abyBuffer.resize(nFeatureSize);
    2093        2646 :         CPL_IGNORE_RET_VAL(
    2094        2646 :             VSIFWriteL(m_abyBuffer.data(), 1, m_abyBuffer.size(), m_fpTable));
    2095             :     }
    2096           0 :     catch (const std::exception &e)
    2097             :     {
    2098           0 :         CPLDebug("OpenFileGDB", "Could not blank deleted feature: %s",
    2099           0 :                  e.what());
    2100             :     }
    2101             : 
    2102        2646 :     m_nValidRecordCount--;
    2103        2646 :     m_bDirtyHeader = true;
    2104             : 
    2105        2646 :     m_bDirtyIndices = true;
    2106             : 
    2107        2646 :     return true;
    2108             : }
    2109             : 
    2110             : /************************************************************************/
    2111             : /*               WholeFileRewriter::~WholeFileRewriter()                */
    2112             : /************************************************************************/
    2113             : 
    2114          45 : FileGDBTable::WholeFileRewriter::~WholeFileRewriter()
    2115             : {
    2116          45 :     if (m_bIsInit)
    2117           6 :         Rollback();
    2118          45 : }
    2119             : 
    2120             : /************************************************************************/
    2121             : /*                    WholeFileRewriter::Begin()                        */
    2122             : /************************************************************************/
    2123             : 
    2124          45 : bool FileGDBTable::WholeFileRewriter::Begin()
    2125             : {
    2126          45 :     m_bOldDirtyIndices = m_oTable.m_bDirtyIndices;
    2127          45 :     m_oTable.RemoveIndices();
    2128          45 :     m_oTable.m_bDirtyIndices = false;
    2129          45 :     if (!m_oTable.Sync())
    2130           0 :         return false;
    2131             : 
    2132             :     // On Windows, we might have issues renaming opened files, even if trying
    2133             :     // to close them before, so updating opened files is less risky.
    2134          45 :     m_bModifyInPlace =
    2135          45 :         CPLTestBool(CPLGetConfigOption("OPENFILEGDB_MODIFY_IN_PLACE",
    2136             : #ifdef _WIN32
    2137             :                                        "YES"
    2138             : #else
    2139             :                                        "NO"
    2140             : #endif
    2141             :                                        ));
    2142             : 
    2143             :     m_osGdbTablx = CPLFormFilename(
    2144          45 :         CPLGetPath(m_oTable.m_osFilename.c_str()),
    2145          45 :         CPLGetBasename(m_oTable.m_osFilename.c_str()), "gdbtablx");
    2146             : 
    2147             :     m_osBackupGdbTable =
    2148          45 :         CPLResetExtension(m_oTable.m_osFilename.c_str(), "_backup.gdbtable");
    2149             :     VSIStatBufL sStat;
    2150          45 :     if (VSIStatL(m_osBackupGdbTable.c_str(), &sStat) == 0)
    2151             :     {
    2152           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2153             :                  "Cannot create backup file %s as it already exists",
    2154             :                  m_osBackupGdbTable.c_str());
    2155           0 :         return false;
    2156             :     }
    2157             : 
    2158             :     m_osBackupGdbTablx =
    2159          45 :         CPLResetExtension(m_osGdbTablx.c_str(), "_backup.gdbtablx");
    2160             : 
    2161          45 :     if (m_bModifyInPlace)
    2162             :     {
    2163             :         // Create backups of .gdtable and .gdtablx if something wrongs happen
    2164          14 :         if (CPLCopyFile(m_osBackupGdbTable.c_str(),
    2165          28 :                         m_oTable.m_osFilename.c_str()) != 0)
    2166             :         {
    2167           0 :             VSIUnlink(m_osBackupGdbTable.c_str());
    2168           0 :             m_osBackupGdbTable.clear();
    2169           0 :             return false;
    2170             :         }
    2171             : 
    2172          14 :         if (CPLCopyFile(m_osBackupGdbTablx.c_str(), m_osGdbTablx.c_str()) != 0)
    2173             :         {
    2174           0 :             VSIUnlink(m_osBackupGdbTable.c_str());
    2175           0 :             VSIUnlink(m_osBackupGdbTablx.c_str());
    2176           0 :             m_osBackupGdbTable.clear();
    2177           0 :             m_osBackupGdbTablx.clear();
    2178           0 :             return false;
    2179             :         }
    2180             : 
    2181          14 :         m_osBackupValidFilename = m_oTable.m_osFilename + ".backup_valid";
    2182          14 :         VSILFILE *fp = VSIFOpenL(m_osBackupValidFilename.c_str(), "wb");
    2183          14 :         if (fp != nullptr)
    2184          14 :             VSIFCloseL(fp);
    2185             : 
    2186          14 :         m_fpOldGdbtable = VSIFOpenL(m_osBackupGdbTable.c_str(), "rb");
    2187          14 :         if (m_fpOldGdbtable == nullptr)
    2188             :         {
    2189           0 :             VSIUnlink(m_osBackupValidFilename.c_str());
    2190           0 :             VSIUnlink(m_osBackupGdbTable.c_str());
    2191           0 :             VSIUnlink(m_osBackupGdbTablx.c_str());
    2192           0 :             m_osBackupValidFilename.clear();
    2193           0 :             m_osBackupGdbTable.clear();
    2194           0 :             m_osBackupGdbTablx.clear();
    2195           0 :             return false;
    2196             :         }
    2197             : 
    2198          14 :         m_fpOldGdbtablx = m_oTable.m_fpTableX;
    2199          14 :         m_fpTable = m_oTable.m_fpTable;
    2200          14 :         m_fpTableX = m_oTable.m_fpTableX;
    2201             :     }
    2202             :     else
    2203             :     {
    2204          31 :         m_osTmpGdbTable = CPLResetExtension(m_oTable.m_osFilename.c_str(),
    2205          31 :                                             "_compress.gdbtable");
    2206             :         m_osTmpGdbTablx =
    2207          31 :             CPLResetExtension(m_osGdbTablx.c_str(), "_compress.gdbtablx");
    2208             : 
    2209          31 :         m_fpOldGdbtable = m_oTable.m_fpTable;
    2210          31 :         m_fpOldGdbtablx = m_oTable.m_fpTableX;
    2211             : 
    2212          31 :         m_fpTable = VSIFOpenL(m_osTmpGdbTable.c_str(), "wb+");
    2213          31 :         if (m_fpTable == nullptr)
    2214             :         {
    2215           0 :             return false;
    2216             :         }
    2217             : 
    2218          31 :         m_fpTableX = VSIFOpenL(m_osTmpGdbTablx.c_str(), "wb+");
    2219          31 :         if (m_fpTableX == nullptr)
    2220             :         {
    2221           0 :             VSIFCloseL(m_fpTable);
    2222           0 :             m_fpTable = nullptr;
    2223           0 :             VSIUnlink(m_osTmpGdbTable.c_str());
    2224           0 :             return false;
    2225             :         }
    2226             : 
    2227          31 :         if (!m_oTable.WriteHeaderX(m_fpTableX))
    2228             :         {
    2229           0 :             VSIFCloseL(m_fpTable);
    2230           0 :             m_fpTable = nullptr;
    2231           0 :             VSIFCloseL(m_fpTableX);
    2232           0 :             m_fpTableX = nullptr;
    2233           0 :             VSIUnlink(m_osTmpGdbTable.c_str());
    2234           0 :             VSIUnlink(m_osTmpGdbTablx.c_str());
    2235           0 :             m_osTmpGdbTable.clear();
    2236           0 :             m_osTmpGdbTablx.clear();
    2237           0 :             return false;
    2238             :         }
    2239             :     }
    2240             : 
    2241          45 :     m_nOldFileSize = m_oTable.m_nFileSize;
    2242          45 :     m_nOldOffsetFieldDesc = m_oTable.m_nOffsetFieldDesc;
    2243          45 :     m_nOldFieldDescLength = m_oTable.m_nFieldDescLength;
    2244          45 :     m_bIsInit = true;
    2245             : 
    2246          45 :     if (!m_oTable.WriteHeader(m_fpTable))
    2247             :     {
    2248           0 :         Rollback();
    2249           0 :         return false;
    2250             :     }
    2251          45 :     if (m_bModifyInPlace)
    2252             :     {
    2253          14 :         VSIFTruncateL(m_fpTable, m_oTable.m_nFileSize);
    2254             :     }
    2255             : 
    2256             :     // Rewrite field descriptors
    2257          45 :     if (!m_oTable.Sync(m_fpTable, m_fpTableX))
    2258             :     {
    2259           0 :         Rollback();
    2260           0 :         return false;
    2261             :     }
    2262             : 
    2263          45 :     VSIFSeekL(m_fpTable, m_oTable.m_nFileSize, SEEK_SET);
    2264             : 
    2265          45 :     return true;
    2266             : }
    2267             : 
    2268             : /************************************************************************/
    2269             : /*                    WholeFileRewriter::Commit()                       */
    2270             : /************************************************************************/
    2271             : 
    2272          39 : bool FileGDBTable::WholeFileRewriter::Commit()
    2273             : {
    2274          39 :     m_oTable.m_bDirtyTableXTrailer = true;
    2275          39 :     m_oTable.m_bDirtyHeader = true;
    2276          39 :     if (!m_oTable.Sync(m_fpTable, m_fpTableX))
    2277             :     {
    2278           0 :         Rollback();
    2279           0 :         return false;
    2280             :     }
    2281             : 
    2282          39 :     if (m_bModifyInPlace)
    2283             :     {
    2284          12 :         VSIFCloseL(m_fpOldGdbtable);
    2285          12 :         VSIUnlink(m_osBackupValidFilename.c_str());
    2286          12 :         VSIUnlink(m_osBackupGdbTable.c_str());
    2287          12 :         VSIUnlink(m_osBackupGdbTablx.c_str());
    2288             :     }
    2289             :     else
    2290             :     {
    2291          27 :         VSIFCloseL(m_oTable.m_fpTable);
    2292          27 :         VSIFCloseL(m_oTable.m_fpTableX);
    2293          27 :         m_oTable.m_fpTable = nullptr;
    2294          27 :         m_oTable.m_fpTableX = nullptr;
    2295             : 
    2296             :         const bool bUseWIN32CodePath =
    2297          27 :             CPLTestBool(CPLGetConfigOption("OPENFILEGDB_SIMUL_WIN32",
    2298             : #ifdef _WIN32
    2299             :                                            "YES"
    2300             : #else
    2301             :                                            "NO"
    2302             : #endif
    2303             :                                            ));
    2304             : 
    2305          27 :         if (bUseWIN32CodePath)
    2306             :         {
    2307             :             // Renaming over an open file doesn't work on Windows
    2308          12 :             VSIFCloseL(m_fpTable);
    2309          12 :             VSIFCloseL(m_fpTableX);
    2310          12 :             m_fpTable = nullptr;
    2311          12 :             m_fpTableX = nullptr;
    2312             : 
    2313             :             // _wrename() on Windows doesn't honour POSIX semantics and forbids
    2314             :             // renaming over an existing file, hence create a temporary backup
    2315          12 :             if (VSIRename(m_oTable.m_osFilename.c_str(),
    2316          12 :                           m_osBackupGdbTable.c_str()) != 0)
    2317             :             {
    2318           0 :                 m_oTable.m_fpTable =
    2319           0 :                     VSIFOpenL(m_oTable.m_osFilename.c_str(), "rb+");
    2320           0 :                 m_oTable.m_fpTableX = VSIFOpenL(m_osGdbTablx.c_str(), "rb+");
    2321           0 :                 Rollback();
    2322           0 :                 return false;
    2323             :             }
    2324             : 
    2325          12 :             if (VSIRename(m_osGdbTablx.c_str(), m_osBackupGdbTablx.c_str()) !=
    2326             :                 0)
    2327             :             {
    2328           0 :                 CPLError(CE_Failure, CPLE_FileIO,
    2329             :                          "Renaming of %s onto %s failed, but renaming of "
    2330             :                          "%s onto %s succeeded. Dataset in corrupt state",
    2331             :                          m_osGdbTablx.c_str(), m_osBackupGdbTablx.c_str(),
    2332           0 :                          m_oTable.m_osFilename.c_str(),
    2333             :                          m_osBackupGdbTable.c_str());
    2334           0 :                 Rollback();
    2335           0 :                 return false;
    2336             :             }
    2337             :         }
    2338             :         else
    2339             :         {
    2340          15 :             m_oTable.m_fpTable = m_fpTable;
    2341          15 :             m_oTable.m_fpTableX = m_fpTableX;
    2342             :         }
    2343             : 
    2344          27 :         if (VSIRename(m_osTmpGdbTable.c_str(), m_oTable.m_osFilename.c_str()) !=
    2345             :             0)
    2346             :         {
    2347           0 :             CPLError(CE_Failure, CPLE_FileIO, "Renaming of %s onto %s failed",
    2348           0 :                      m_osTmpGdbTable.c_str(), m_oTable.m_osFilename.c_str());
    2349           0 :             Rollback();
    2350           0 :             return false;
    2351             :         }
    2352             : 
    2353          27 :         if (VSIRename(m_osTmpGdbTablx.c_str(), m_osGdbTablx.c_str()) != 0)
    2354             :         {
    2355           0 :             CPLError(CE_Failure, CPLE_FileIO, "Renaming of %s onto %s failed",
    2356             :                      m_osTmpGdbTablx.c_str(), m_osGdbTablx.c_str());
    2357           0 :             Rollback();
    2358           0 :             return false;
    2359             :         }
    2360             : 
    2361          27 :         if (bUseWIN32CodePath)
    2362             :         {
    2363          24 :             m_oTable.m_fpTable =
    2364          12 :                 VSIFOpenL(m_oTable.m_osFilename.c_str(), "rb+");
    2365          12 :             m_oTable.m_fpTableX = VSIFOpenL(m_osGdbTablx.c_str(), "rb+");
    2366          12 :             VSIUnlink(m_osBackupGdbTable.c_str());
    2367          12 :             VSIUnlink(m_osBackupGdbTablx.c_str());
    2368             :         }
    2369             :     }
    2370             : 
    2371          39 :     m_oTable.DeleteFreeList();
    2372          39 :     if (m_bOldDirtyIndices)
    2373             :     {
    2374           8 :         m_oTable.m_bDirtyIndices = true;
    2375           8 :         m_oTable.Sync();
    2376             :     }
    2377             : 
    2378          39 :     m_bIsInit = false;
    2379             : 
    2380          39 :     return true;
    2381             : }
    2382             : 
    2383             : /************************************************************************/
    2384             : /*                   WholeFileRewriter::Rollback()                      */
    2385             : /************************************************************************/
    2386             : 
    2387           6 : void FileGDBTable::WholeFileRewriter::Rollback()
    2388             : {
    2389           6 :     CPLAssert(m_bIsInit);
    2390           6 :     m_bIsInit = false;
    2391             : 
    2392           6 :     if (m_bModifyInPlace)
    2393             :     {
    2394           2 :         VSIFCloseL(m_fpOldGdbtable);
    2395           2 :         m_fpOldGdbtable = nullptr;
    2396             : 
    2397             :         // Try to restore from backup files in case of failure
    2398           2 :         if (CPLCopyFile(m_oTable.m_osFilename.c_str(),
    2399           4 :                         m_osBackupGdbTable.c_str()) == 0 &&
    2400           2 :             CPLCopyFile(m_osGdbTablx.c_str(), m_osBackupGdbTablx.c_str()) == 0)
    2401             :         {
    2402           2 :             VSIUnlink(m_osBackupValidFilename.c_str());
    2403           2 :             VSIUnlink(m_osBackupGdbTable.c_str());
    2404           2 :             VSIUnlink(m_osBackupGdbTablx.c_str());
    2405             :         }
    2406             :         else
    2407             :         {
    2408           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    2409             :                      "%s and %s are corrupted, and couldn't be restored from "
    2410             :                      "their backups %s and %s. You'll have to manually replace "
    2411             :                      "the former files by the latter ones.",
    2412           0 :                      m_oTable.m_osFilename.c_str(), m_osGdbTablx.c_str(),
    2413             :                      m_osBackupGdbTable.c_str(), m_osBackupGdbTablx.c_str());
    2414             :         }
    2415             :     }
    2416             :     else
    2417             :     {
    2418           4 :         VSIFCloseL(m_fpTable);
    2419           4 :         VSIFCloseL(m_fpTableX);
    2420           4 :         m_fpTable = nullptr;
    2421           4 :         m_fpTableX = nullptr;
    2422           4 :         VSIUnlink(m_osTmpGdbTable.c_str());
    2423           4 :         VSIUnlink(m_osTmpGdbTablx.c_str());
    2424             :     }
    2425             : 
    2426           6 :     m_oTable.m_nFileSize = m_nOldFileSize;
    2427           6 :     m_oTable.m_nOffsetFieldDesc = m_nOldOffsetFieldDesc;
    2428           6 :     m_oTable.m_nFieldDescLength = m_nOldFieldDescLength;
    2429             : 
    2430           6 :     m_oTable.m_bDirtyFieldDescriptors = false;
    2431           6 :     m_oTable.m_bDirtyTableXHeader = false;
    2432           6 :     m_oTable.m_bDirtyTableXTrailer = false;
    2433           6 :     m_oTable.m_bDirtyHeader = false;
    2434           6 : }
    2435             : 
    2436             : /************************************************************************/
    2437             : /*                                Repack()                              */
    2438             : /************************************************************************/
    2439             : 
    2440           4 : bool FileGDBTable::Repack()
    2441             : {
    2442           4 :     if (!m_bUpdate || !Sync())
    2443           0 :         return false;
    2444             : 
    2445           4 :     bool bRepackNeeded = false;
    2446           4 :     if (m_nOffsetFieldDesc > 40)
    2447             :     {
    2448             :         // If the field descriptor section is not at offset 40, it is possible
    2449             :         // that there's our "ghost area" there.
    2450           4 :         GByte abyBuffer[8] = {0};
    2451           4 :         VSIFSeekL(m_fpTable, 40, SEEK_SET);
    2452           4 :         VSIFReadL(abyBuffer, 1, sizeof(abyBuffer), m_fpTable);
    2453           8 :         if (!(memcmp(abyBuffer + 4, "GDAL", 4) == 0 &&
    2454           4 :               static_cast<uint64_t>(40) + sizeof(uint32_t) +
    2455           4 :                       GetUInt32(abyBuffer, 0) ==
    2456           4 :                   m_nOffsetFieldDesc))
    2457             :         {
    2458           0 :             CPLDebug("OpenFileGDB",
    2459             :                      "Repack(%s): field descriptors not at beginning of file",
    2460             :                      m_osFilename.c_str());
    2461           0 :             bRepackNeeded = true;
    2462             :         }
    2463             :     }
    2464             : 
    2465           4 :     uint64_t nExpectedOffset =
    2466           4 :         m_nOffsetFieldDesc + sizeof(uint32_t) + m_nFieldDescLength;
    2467             : 
    2468           8 :     std::vector<GByte> abyBufferOffsets;
    2469           4 :     abyBufferOffsets.resize(TABLX_FEATURES_PER_PAGE * m_nTablxOffsetSize);
    2470             : 
    2471             :     // Scan all features
    2472           8 :     for (uint32_t iPage = 0; !bRepackNeeded && iPage < m_n1024BlocksPresent;
    2473             :          ++iPage)
    2474             :     {
    2475           4 :         const vsi_l_offset nOffsetInTableX =
    2476           4 :             TABLX_HEADER_SIZE + m_nTablxOffsetSize *
    2477           4 :                                     static_cast<vsi_l_offset>(iPage) *
    2478             :                                     TABLX_FEATURES_PER_PAGE;
    2479           4 :         VSIFSeekL(m_fpTableX, nOffsetInTableX, SEEK_SET);
    2480           4 :         if (VSIFReadL(abyBufferOffsets.data(),
    2481           4 :                       m_nTablxOffsetSize * TABLX_FEATURES_PER_PAGE, 1,
    2482           4 :                       m_fpTableX) != 1)
    2483           0 :             return false;
    2484             : 
    2485           4 :         GByte *pabyBufferOffsets = abyBufferOffsets.data();
    2486        3077 :         for (int i = 0; i < TABLX_FEATURES_PER_PAGE;
    2487        3073 :              i++, pabyBufferOffsets += m_nTablxOffsetSize)
    2488             :         {
    2489        3074 :             const uint64_t nOffset = ReadFeatureOffset(pabyBufferOffsets);
    2490        3074 :             if (nOffset != 0)
    2491             :             {
    2492           7 :                 if (!bRepackNeeded && nOffset != nExpectedOffset)
    2493             :                 {
    2494           1 :                     bRepackNeeded = true;
    2495           1 :                     CPLDebug("OpenFileGDB",
    2496             :                              "Repack(%s): feature at offset " CPL_FRMT_GUIB
    2497             :                              " instead of " CPL_FRMT_GUIB ". Repack needed",
    2498             :                              m_osFilename.c_str(),
    2499             :                              static_cast<GUIntBig>(nOffset),
    2500             :                              static_cast<GUIntBig>(nExpectedOffset));
    2501           1 :                     break;
    2502             :                 }
    2503             : 
    2504             :                 // Read feature size
    2505           6 :                 VSIFSeekL(m_fpTable, nOffset, SEEK_SET);
    2506           6 :                 uint32_t nFeatureSize = 0;
    2507           6 :                 if (!ReadUInt32(m_fpTable, nFeatureSize))
    2508           0 :                     return false;
    2509             : 
    2510           6 :                 nExpectedOffset += sizeof(uint32_t);
    2511           6 :                 nExpectedOffset += nFeatureSize;
    2512             :             }
    2513             :         }
    2514             :     }
    2515             : 
    2516           4 :     if (!bRepackNeeded)
    2517             :     {
    2518           3 :         if (m_nFileSize > nExpectedOffset)
    2519             :         {
    2520           1 :             CPLDebug("OpenFileGDB",
    2521             :                      "Deleted features at end of file. Truncating it");
    2522             : 
    2523           1 :             m_nFileSize = nExpectedOffset;
    2524           1 :             VSIFTruncateL(m_fpTable, m_nFileSize);
    2525           1 :             m_bDirtyHeader = true;
    2526             : 
    2527           1 :             DeleteFreeList();
    2528             : 
    2529           1 :             return Sync();
    2530             :         }
    2531             : 
    2532           2 :         CPLDebug("OpenFileGDB", "Repack(%s): file already compacted",
    2533             :                  m_osFilename.c_str());
    2534           2 :         return true;
    2535             :     }
    2536             : 
    2537           2 :     WholeFileRewriter oWholeFileRewriter(*this);
    2538           1 :     if (!oWholeFileRewriter.Begin())
    2539           0 :         return false;
    2540             : 
    2541           1 :     uint32_t nRowBufferMaxSize = 0;
    2542           1 :     m_nCurRow = -1;
    2543             : 
    2544             :     // Rewrite all features
    2545           2 :     for (uint32_t iPage = 0; iPage < m_n1024BlocksPresent; ++iPage)
    2546             :     {
    2547           1 :         const vsi_l_offset nOffsetInTableX =
    2548           1 :             TABLX_HEADER_SIZE + m_nTablxOffsetSize *
    2549           1 :                                     static_cast<vsi_l_offset>(iPage) *
    2550             :                                     TABLX_FEATURES_PER_PAGE;
    2551           1 :         VSIFSeekL(oWholeFileRewriter.m_fpOldGdbtablx, nOffsetInTableX,
    2552             :                   SEEK_SET);
    2553           1 :         if (VSIFReadL(abyBufferOffsets.data(),
    2554           1 :                       m_nTablxOffsetSize * TABLX_FEATURES_PER_PAGE, 1,
    2555           1 :                       oWholeFileRewriter.m_fpOldGdbtablx) != 1)
    2556           0 :             return false;
    2557             : 
    2558           1 :         GByte *pabyBufferOffsets = abyBufferOffsets.data();
    2559        1025 :         for (int i = 0; i < TABLX_FEATURES_PER_PAGE;
    2560        1024 :              i++, pabyBufferOffsets += m_nTablxOffsetSize)
    2561             :         {
    2562        1024 :             const uint64_t nOffset = ReadFeatureOffset(pabyBufferOffsets);
    2563        1024 :             if (nOffset != 0)
    2564             :             {
    2565             :                 // Read feature size
    2566           1 :                 VSIFSeekL(oWholeFileRewriter.m_fpOldGdbtable, nOffset,
    2567             :                           SEEK_SET);
    2568           1 :                 uint32_t nFeatureSize = 0;
    2569           1 :                 if (!ReadUInt32(oWholeFileRewriter.m_fpOldGdbtable,
    2570             :                                 nFeatureSize))
    2571           0 :                     return false;
    2572             : 
    2573             :                 // Read feature data
    2574           1 :                 if (nFeatureSize > m_abyBuffer.size())
    2575             :                 {
    2576             :                     try
    2577             :                     {
    2578           0 :                         m_abyBuffer.resize(nFeatureSize);
    2579             :                     }
    2580           0 :                     catch (const std::exception &e)
    2581             :                     {
    2582           0 :                         CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
    2583           0 :                         return false;
    2584             :                     }
    2585             :                 }
    2586           1 :                 if (VSIFReadL(m_abyBuffer.data(), nFeatureSize, 1,
    2587           1 :                               oWholeFileRewriter.m_fpOldGdbtable) != 1)
    2588           0 :                     return false;
    2589             : 
    2590             :                 // Update offset of updated feature
    2591           1 :                 WriteFeatureOffset(m_nFileSize, pabyBufferOffsets);
    2592             : 
    2593             :                 // Write feature size
    2594           1 :                 if (!WriteUInt32(oWholeFileRewriter.m_fpTable, nFeatureSize))
    2595           0 :                     return false;
    2596           1 :                 if (VSIFWriteL(m_abyBuffer.data(), nFeatureSize, 1,
    2597           1 :                                oWholeFileRewriter.m_fpTable) != 1)
    2598           0 :                     return false;
    2599             : 
    2600           1 :                 if (nFeatureSize > nRowBufferMaxSize)
    2601           1 :                     nRowBufferMaxSize = nFeatureSize;
    2602           1 :                 m_nFileSize += sizeof(uint32_t) + nFeatureSize;
    2603             :             }
    2604             :         }
    2605           1 :         VSIFSeekL(oWholeFileRewriter.m_fpTableX, nOffsetInTableX, SEEK_SET);
    2606           1 :         if (VSIFWriteL(abyBufferOffsets.data(),
    2607           1 :                        m_nTablxOffsetSize * TABLX_FEATURES_PER_PAGE, 1,
    2608           1 :                        oWholeFileRewriter.m_fpTableX) != 1)
    2609           0 :             return false;
    2610             :     }
    2611             : 
    2612           1 :     m_nRowBufferMaxSize = nRowBufferMaxSize;
    2613           1 :     m_nHeaderBufferMaxSize = std::max(m_nFieldDescLength, m_nRowBufferMaxSize);
    2614             : 
    2615           1 :     return oWholeFileRewriter.Commit();
    2616             : }
    2617             : 
    2618             : /************************************************************************/
    2619             : /*                          RecomputeExtent()                           */
    2620             : /************************************************************************/
    2621             : 
    2622           2 : void FileGDBTable::RecomputeExtent()
    2623             : {
    2624           2 :     if (!m_bUpdate || m_iGeomField < 0)
    2625           0 :         return;
    2626             : 
    2627             :     // Scan all features
    2628           2 :     OGREnvelope sLayerEnvelope;
    2629           2 :     OGREnvelope sFeatureEnvelope;
    2630           6 :     for (int iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat)
    2631             :     {
    2632           4 :         iCurFeat = GetAndSelectNextNonEmptyRow(iCurFeat);
    2633           4 :         if (iCurFeat < 0)
    2634           0 :             break;
    2635           4 :         const auto psGeomField = GetFieldValue(m_iGeomField);
    2636           4 :         if (psGeomField && GetFeatureExtent(psGeomField, &sFeatureEnvelope))
    2637             :         {
    2638           2 :             sLayerEnvelope.Merge(sFeatureEnvelope);
    2639             :         }
    2640             :     }
    2641             : 
    2642           2 :     m_bDirtyGeomFieldBBox = true;
    2643             :     auto poGeomField =
    2644           2 :         cpl::down_cast<FileGDBGeomField *>(m_apoFields[m_iGeomField].get());
    2645           2 :     if (sLayerEnvelope.IsInit())
    2646             :     {
    2647           1 :         poGeomField->SetXYMinMax(sLayerEnvelope.MinX, sLayerEnvelope.MinY,
    2648             :                                  sLayerEnvelope.MaxX, sLayerEnvelope.MaxY);
    2649             :     }
    2650             :     else
    2651             :     {
    2652           1 :         poGeomField->SetXYMinMax(
    2653             :             FileGDBGeomField::ESRI_NAN, FileGDBGeomField::ESRI_NAN,
    2654             :             FileGDBGeomField::ESRI_NAN, FileGDBGeomField::ESRI_NAN);
    2655             :     }
    2656             : }
    2657             : 
    2658             : } /* namespace OpenFileGDB */

Generated by: LCOV version 1.14