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

Generated by: LCOV version 1.14