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

Generated by: LCOV version 1.14