LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/openfilegdb - ogropenfilegdblayer_write.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1439 1659 86.7 %
Date: 2026-04-15 22:10:00 Functions: 36 37 97.3 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Implements Open FileGDB OGR driver.
       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             : #include "ogr_openfilegdb.h"
      15             : #include "filegdb_gdbtoogrfieldtype.h"
      16             : 
      17             : #include <cinttypes>
      18             : #include <cmath>
      19             : #include <cstddef>
      20             : #include <cstdio>
      21             : #include <cstdlib>
      22             : #include <cstring>
      23             : #include <cwchar>
      24             : #include <algorithm>
      25             : #include <limits>
      26             : #include <string>
      27             : 
      28             : #include "cpl_conv.h"
      29             : #include "cpl_error.h"
      30             : #include "cpl_minixml.h"
      31             : #include "cpl_string.h"
      32             : #include "gdal_priv_templates.hpp"
      33             : #include "ogr_api.h"
      34             : #include "ogr_core.h"
      35             : #include "ogr_feature.h"
      36             : #include "ogr_geometry.h"
      37             : #include "ogr_spatialref.h"
      38             : #include "ogr_srs_api.h"
      39             : #include "ogrsf_frmts.h"
      40             : #include "filegdbtable.h"
      41             : #include "filegdbtable_priv.h"
      42             : #include "filegdb_coordprec_write.h"
      43             : #include "filegdb_reserved_keywords.h"
      44             : 
      45             : /************************************************************************/
      46             : /*                          StringToWString()                           */
      47             : /************************************************************************/
      48             : 
      49        3423 : static std::wstring StringToWString(const std::string &utf8string)
      50             : {
      51             :     wchar_t *pszUTF16 =
      52        3423 :         CPLRecodeToWChar(utf8string.c_str(), CPL_ENC_UTF8, CPL_ENC_UCS2);
      53        3423 :     std::wstring utf16string(pszUTF16);
      54        3423 :     CPLFree(pszUTF16);
      55        3423 :     return utf16string;
      56             : }
      57             : 
      58             : /************************************************************************/
      59             : /*                          WStringToString()                           */
      60             : /************************************************************************/
      61             : 
      62        4765 : static std::string WStringToString(const std::wstring &utf16string)
      63             : {
      64             :     char *pszUTF8 =
      65        4765 :         CPLRecodeFromWChar(utf16string.c_str(), CPL_ENC_UCS2, CPL_ENC_UTF8);
      66        4765 :     std::string utf8string(pszUTF8);
      67        4765 :     CPLFree(pszUTF8);
      68        4765 :     return utf8string;
      69             : }
      70             : 
      71             : /************************************************************************/
      72             : /*                            LaunderName()                             */
      73             : /************************************************************************/
      74             : 
      75        1181 : static std::wstring LaunderName(const std::wstring &name)
      76             : {
      77        1181 :     std::wstring newName = name;
      78             : 
      79             :     // https://support.esri.com/en/technical-article/000005588
      80             : 
      81             :     // "Do not start field or table names with an underscore or a number."
      82             :     // But we can see in the wild table names starting with underscore...
      83             :     // (cf https://github.com/OSGeo/gdal/issues/4112)
      84        1181 :     if (!newName.empty() && newName[0] >= '0' && newName[0] <= '9')
      85             :     {
      86           2 :         newName = StringToWString("_") + newName;
      87             :     }
      88             : 
      89             :     // "Essentially, eliminate anything that is not alphanumeric or an
      90             :     // underscore." Note: alphanumeric unicode is supported
      91        9445 :     for (size_t i = 0; i < newName.size(); i++)
      92             :     {
      93       15283 :         if (!(newName[i] == '_' || (newName[i] >= '0' && newName[i] <= '9') ||
      94        7019 :               (newName[i] >= 'a' && newName[i] <= 'z') ||
      95         688 :               (newName[i] >= 'A' && newName[i] <= 'Z') || newName[i] >= 128))
      96             :         {
      97          61 :             newName[i] = '_';
      98             :         }
      99             :     }
     100             : 
     101        1181 :     return newName;
     102             : }
     103             : 
     104             : /************************************************************************/
     105             : /*                     EscapeUnsupportedPrefixes()                      */
     106             : /************************************************************************/
     107             : 
     108         337 : static std::wstring EscapeUnsupportedPrefixes(const std::wstring &className)
     109             : {
     110         337 :     std::wstring newName = className;
     111             :     // From ESRI docs
     112             :     // Feature classes starting with these strings are unsupported.
     113             :     static const char *const UNSUPPORTED_PREFIXES[] = {"sde_", "gdb_", "delta_",
     114             :                                                        nullptr};
     115             : 
     116        1345 :     for (int i = 0; UNSUPPORTED_PREFIXES[i] != nullptr; i++)
     117             :     {
     118             :         // cppcheck-suppress stlIfStrFind
     119        1009 :         if (newName.find(StringToWString(UNSUPPORTED_PREFIXES[i])) == 0)
     120             :         {
     121             :             // Normally table names shouldn't start with underscore, but
     122             :             // there are such in the wild (cf
     123             :             // https://github.com/OSGeo/gdal/issues/4112)
     124           1 :             newName = StringToWString("_") + newName;
     125           1 :             break;
     126             :         }
     127             :     }
     128             : 
     129         337 :     return newName;
     130             : }
     131             : 
     132             : /************************************************************************/
     133             : /*                       EscapeReservedKeywords()                       */
     134             : /************************************************************************/
     135             : 
     136        1200 : static std::wstring EscapeReservedKeywords(const std::wstring &name)
     137             : {
     138        2400 :     std::string newName = WStringToString(name);
     139        2400 :     std::string upperName = CPLString(newName).toupper();
     140             : 
     141             :     // Append an underscore to any FGDB reserved words used as field names
     142             :     // This is the same behavior ArcCatalog follows.
     143       34760 :     for (const char *pszKeyword : apszRESERVED_WORDS)
     144             :     {
     145       33563 :         if (upperName == pszKeyword)
     146             :         {
     147           3 :             newName += '_';
     148           3 :             break;
     149             :         }
     150             :     }
     151             : 
     152        2400 :     return StringToWString(newName);
     153             : }
     154             : 
     155             : /************************************************************************/
     156             : /*                     XMLSerializeGeomFieldBase()                      */
     157             : /************************************************************************/
     158             : 
     159         930 : static void XMLSerializeGeomFieldBase(CPLXMLNode *psRoot,
     160             :                                       const FileGDBGeomField *poGeomFieldDefn,
     161             :                                       const OGRSpatialReference *poSRS)
     162             : {
     163         930 :     auto psExtent = CPLCreateXMLElementAndValue(psRoot, "Extent", "");
     164         930 :     CPLAddXMLAttributeAndValue(psExtent, "xsi:nil", "true");
     165             : 
     166             :     auto psSpatialReference =
     167         930 :         CPLCreateXMLNode(psRoot, CXT_Element, "SpatialReference");
     168             : 
     169         930 :     if (poSRS == nullptr)
     170             :     {
     171         417 :         CPLAddXMLAttributeAndValue(psSpatialReference, "xsi:type",
     172             :                                    "typens:UnknownCoordinateSystem");
     173             :     }
     174             :     else
     175             :     {
     176         513 :         if (poSRS->IsGeographic())
     177         485 :             CPLAddXMLAttributeAndValue(psSpatialReference, "xsi:type",
     178             :                                        "typens:GeographicCoordinateSystem");
     179             :         else
     180          28 :             CPLAddXMLAttributeAndValue(psSpatialReference, "xsi:type",
     181             :                                        "typens:ProjectedCoordinateSystem");
     182         513 :         CPLCreateXMLElementAndValue(psSpatialReference, "WKT",
     183         513 :                                     poGeomFieldDefn->GetWKT().c_str());
     184             :     }
     185         930 :     CPLCreateXMLElementAndValue(
     186             :         psSpatialReference, "XOrigin",
     187             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetXOrigin()));
     188         930 :     CPLCreateXMLElementAndValue(
     189             :         psSpatialReference, "YOrigin",
     190             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetYOrigin()));
     191         930 :     CPLCreateXMLElementAndValue(
     192             :         psSpatialReference, "XYScale",
     193             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetXYScale()));
     194         930 :     CPLCreateXMLElementAndValue(
     195             :         psSpatialReference, "ZOrigin",
     196             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetZOrigin()));
     197         930 :     CPLCreateXMLElementAndValue(
     198             :         psSpatialReference, "ZScale",
     199             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetZScale()));
     200         930 :     CPLCreateXMLElementAndValue(
     201             :         psSpatialReference, "MOrigin",
     202             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetMOrigin()));
     203         930 :     CPLCreateXMLElementAndValue(
     204             :         psSpatialReference, "MScale",
     205             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetMScale()));
     206         930 :     CPLCreateXMLElementAndValue(
     207             :         psSpatialReference, "XYTolerance",
     208             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetXYTolerance()));
     209         930 :     CPLCreateXMLElementAndValue(
     210             :         psSpatialReference, "ZTolerance",
     211             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetZTolerance()));
     212         930 :     CPLCreateXMLElementAndValue(
     213             :         psSpatialReference, "MTolerance",
     214             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetMTolerance()));
     215         930 :     CPLCreateXMLElementAndValue(psSpatialReference, "HighPrecision", "true");
     216         930 :     if (poSRS)
     217             :     {
     218         513 :         if (CPLTestBool(CPLGetConfigOption("OPENFILEGDB_WRITE_WKID", "YES")))
     219             :         {
     220         513 :             const char *pszKey = poSRS->IsProjected() ? "PROJCS" : "GEOGCS";
     221         513 :             const char *pszAuthorityName = poSRS->GetAuthorityName(pszKey);
     222         513 :             const char *pszAuthorityCode = poSRS->GetAuthorityCode(pszKey);
     223         513 :             if (pszAuthorityName && pszAuthorityCode &&
     224         513 :                 (EQUAL(pszAuthorityName, "EPSG") ||
     225           0 :                  EQUAL(pszAuthorityName, "ESRI")))
     226             :             {
     227         513 :                 CPLCreateXMLElementAndValue(psSpatialReference, "WKID",
     228             :                                             pszAuthorityCode);
     229         513 :                 if (CPLTestBool(CPLGetConfigOption(
     230             :                         "OPENFILEGDB_WRITE_LATESTWKID", "YES")))
     231             :                 {
     232         513 :                     CPLCreateXMLElementAndValue(psSpatialReference,
     233             :                                                 "LatestWKID", pszAuthorityCode);
     234             :                 }
     235             :             }
     236             :         }
     237             : 
     238         513 :         if (poSRS->IsCompound() &&
     239           0 :             CPLTestBool(CPLGetConfigOption("OPENFILEGDB_WRITE_VCSWKID", "YES")))
     240             :         {
     241           0 :             const char *pszAuthorityName = poSRS->GetAuthorityName("VERT_CS");
     242           0 :             const char *pszAuthorityCode = poSRS->GetAuthorityCode("VERT_CS");
     243           0 :             if (pszAuthorityName && pszAuthorityCode &&
     244           0 :                 (EQUAL(pszAuthorityName, "EPSG") ||
     245           0 :                  EQUAL(pszAuthorityName, "ESRI")))
     246             :             {
     247           0 :                 CPLCreateXMLElementAndValue(psSpatialReference, "VCSWKID",
     248             :                                             pszAuthorityCode);
     249           0 :                 if (CPLTestBool(CPLGetConfigOption(
     250             :                         "OPENFILEGDB_WRITE_LATESTVCSWKID", "YES")))
     251             :                 {
     252           0 :                     CPLCreateXMLElementAndValue(
     253             :                         psSpatialReference, "LatestVCSWKID", pszAuthorityCode);
     254             :                 }
     255             :             }
     256             :         }
     257             :     }
     258         930 : }
     259             : 
     260             : /************************************************************************/
     261             : /*                        CreateFeatureDataset()                        */
     262             : /************************************************************************/
     263             : 
     264           3 : bool OGROpenFileGDBLayer::CreateFeatureDataset(const char *pszFeatureDataset)
     265             : {
     266           6 :     std::string osPath("\\");
     267           3 :     osPath += pszFeatureDataset;
     268             : 
     269           6 :     CPLXMLTreeCloser oTree(CPLCreateXMLNode(nullptr, CXT_Element, "?xml"));
     270           3 :     CPLAddXMLAttributeAndValue(oTree.get(), "version", "1.0");
     271           3 :     CPLAddXMLAttributeAndValue(oTree.get(), "encoding", "UTF-8");
     272             : 
     273             :     CPLXMLNode *psRoot =
     274           3 :         CPLCreateXMLNode(nullptr, CXT_Element, "typens:DEFeatureDataset");
     275           3 :     CPLAddXMLSibling(oTree.get(), psRoot);
     276             : 
     277           3 :     CPLAddXMLAttributeAndValue(psRoot, "xmlns:xsi",
     278             :                                "http://www.w3.org/2001/XMLSchema-instance");
     279           3 :     CPLAddXMLAttributeAndValue(psRoot, "xmlns:xs",
     280             :                                "http://www.w3.org/2001/XMLSchema");
     281           3 :     CPLAddXMLAttributeAndValue(psRoot, "xmlns:typens",
     282             :                                "http://www.esri.com/schemas/ArcGIS/10.1");
     283           3 :     CPLAddXMLAttributeAndValue(psRoot, "xsi:type", "typens:DEFeatureDataset");
     284             : 
     285           3 :     CPLCreateXMLElementAndValue(psRoot, "CatalogPath", osPath.c_str());
     286           3 :     CPLCreateXMLElementAndValue(psRoot, "Name", pszFeatureDataset);
     287           3 :     CPLCreateXMLElementAndValue(psRoot, "ChildrenExpanded", "false");
     288           3 :     CPLCreateXMLElementAndValue(psRoot, "DatasetType", "esriDTFeatureDataset");
     289             : 
     290             :     {
     291           3 :         FileGDBTable oTable;
     292           3 :         if (!oTable.Open(m_poDS->m_osGDBItemsFilename.c_str(), false))
     293           0 :             return false;
     294           3 :         CPLCreateXMLElementAndValue(
     295             :             psRoot, "DSID",
     296           3 :             CPLSPrintf("%" PRId64, 1 + oTable.GetTotalRecordCount()));
     297             :     }
     298             : 
     299           3 :     CPLCreateXMLElementAndValue(psRoot, "Versioned", "false");
     300           3 :     CPLCreateXMLElementAndValue(psRoot, "CanVersion", "false");
     301             : 
     302           3 :     if (m_eGeomType != wkbNone)
     303             :     {
     304           3 :         XMLSerializeGeomFieldBase(psRoot, m_poLyrTable->GetGeomField(),
     305           3 :                                   GetSpatialRef());
     306             :     }
     307             : 
     308           3 :     char *pszDefinition = CPLSerializeXMLTree(oTree.get());
     309           6 :     const std::string osDefinition(pszDefinition);
     310           3 :     CPLFree(pszDefinition);
     311             : 
     312           3 :     m_osFeatureDatasetGUID = OFGDBGenerateUUID();
     313             : 
     314           6 :     if (!m_poDS->RegisterInItemRelationships(
     315           3 :             m_poDS->m_osRootGUID, m_osFeatureDatasetGUID,
     316             :             "{dc78f1ab-34e4-43ac-ba47-1c4eabd0e7c7}"))
     317             :     {
     318           0 :         return false;
     319             :     }
     320             : 
     321           6 :     if (!m_poDS->RegisterFeatureDatasetInItems(
     322           3 :             m_osFeatureDatasetGUID, pszFeatureDataset, osDefinition.c_str()))
     323             :     {
     324           0 :         return false;
     325             :     }
     326             : 
     327           3 :     return true;
     328             : }
     329             : 
     330             : /************************************************************************/
     331             : /*                       GetLaunderedLayerName()                        */
     332             : /************************************************************************/
     333             : 
     334             : std::string
     335         337 : OGROpenFileGDBLayer::GetLaunderedLayerName(const std::string &osNameOri) const
     336             : {
     337         674 :     std::wstring wlayerName = StringToWString(osNameOri);
     338             : 
     339         337 :     wlayerName = LaunderName(wlayerName);
     340         337 :     wlayerName = EscapeReservedKeywords(wlayerName);
     341         337 :     wlayerName = EscapeUnsupportedPrefixes(wlayerName);
     342             : 
     343             :     // https://desktop.arcgis.com/en/arcmap/latest/manage-data/administer-file-gdbs/file-geodatabase-size-and-name-limits.htm
     344             :     // document 160 character limit but
     345             :     // https://desktop.arcgis.com/en/arcmap/latest/manage-data/tables/fundamentals-of-adding-and-deleting-fields.htm#GUID-8E190093-8F8F-4132-AF4F-B0C9220F76B3
     346             :     // mentions 64. let be optimistic and aim for 160
     347         337 :     constexpr size_t TABLE_NAME_MAX_SIZE = 160;
     348         337 :     if (wlayerName.size() > TABLE_NAME_MAX_SIZE)
     349           4 :         wlayerName.resize(TABLE_NAME_MAX_SIZE);
     350             : 
     351             :     /* Ensures uniqueness of layer name */
     352         337 :     int numRenames = 1;
     353         688 :     while ((m_poDS->GetLayerByName(WStringToString(wlayerName).c_str()) !=
     354        1032 :             nullptr) &&
     355             :            (numRenames < 10))
     356             :     {
     357          21 :         wlayerName = StringToWString(CPLSPrintf(
     358             :             "%s_%d",
     359          14 :             WStringToString(wlayerName.substr(0, TABLE_NAME_MAX_SIZE - 2))
     360             :                 .c_str(),
     361           7 :             numRenames));
     362           7 :         numRenames++;
     363             :     }
     364         674 :     while ((m_poDS->GetLayerByName(WStringToString(wlayerName).c_str()) !=
     365        1011 :             nullptr) &&
     366             :            (numRenames < 100))
     367             :     {
     368           0 :         wlayerName = StringToWString(CPLSPrintf(
     369             :             "%s_%d",
     370           0 :             WStringToString(wlayerName.substr(0, TABLE_NAME_MAX_SIZE - 3))
     371             :                 .c_str(),
     372           0 :             numRenames));
     373           0 :         numRenames++;
     374             :     }
     375             : 
     376         674 :     return WStringToString(wlayerName);
     377             : }
     378             : 
     379             : /************************************************************************/
     380             : /*                               Create()                               */
     381             : /************************************************************************/
     382             : 
     383         329 : bool OGROpenFileGDBLayer::Create(const OGRGeomFieldDefn *poSrcGeomFieldDefn)
     384             : {
     385         329 :     FileGDBTableGeometryType eTableGeomType = FGTGT_NONE;
     386         329 :     const auto eFlattenType = wkbFlatten(OGR_GT_GetLinear(m_eGeomType));
     387         329 :     if (eFlattenType == wkbNone)
     388          87 :         eTableGeomType = FGTGT_NONE;
     389         242 :     else if (CPLTestBool(m_aosCreationOptions.FetchNameValueDef(
     390             :                  "CREATE_MULTIPATCH", "FALSE")))
     391             :     {
     392             :         // For compatibility with FileGDB driver
     393           2 :         eTableGeomType = FGTGT_MULTIPATCH;
     394             :     }
     395         240 :     else if (eFlattenType == wkbPoint)
     396         103 :         eTableGeomType = FGTGT_POINT;
     397         137 :     else if (eFlattenType == wkbMultiPoint)
     398          15 :         eTableGeomType = FGTGT_MULTIPOINT;
     399         239 :     else if (OGR_GT_IsCurve(eFlattenType) ||
     400         117 :              OGR_GT_IsSubClassOf(eFlattenType, wkbMultiCurve))
     401             :     {
     402          47 :         eTableGeomType = FGTGT_LINE;
     403             :     }
     404          75 :     else if (wkbFlatten(m_eGeomType) == wkbTIN ||
     405          72 :              wkbFlatten(m_eGeomType) == wkbPolyhedralSurface ||
     406         216 :              m_eGeomType == wkbGeometryCollection25D ||
     407          69 :              m_eGeomType == wkbSetZ(wkbUnknown))
     408             :     {
     409           6 :         eTableGeomType = FGTGT_MULTIPATCH;
     410             :     }
     411         129 :     else if (OGR_GT_IsSurface(eFlattenType) ||
     412          60 :              OGR_GT_IsSubClassOf(eFlattenType, wkbMultiSurface))
     413             :     {
     414          67 :         eTableGeomType = FGTGT_POLYGON;
     415             :     }
     416             :     else
     417             :     {
     418           2 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported geometry type");
     419           2 :         return false;
     420             :     }
     421             : 
     422         654 :     const std::string osNameOri(m_osName);
     423             :     /* Launder the Layer name */
     424         327 :     m_osName = GetLaunderedLayerName(osNameOri);
     425         327 :     if (osNameOri != m_osName)
     426             :     {
     427          47 :         CPLError(CE_Warning, CPLE_NotSupported,
     428             :                  "Normalized/laundered layer name: '%s' to '%s'",
     429             :                  osNameOri.c_str(), m_osName.c_str());
     430             :     }
     431             : 
     432             :     const char *pszFeatureDataset =
     433         327 :         m_aosCreationOptions.FetchNameValue("FEATURE_DATASET");
     434         654 :     std::string osFeatureDatasetDef;
     435         654 :     OGRSpatialReferenceRefCountedPtr poFeatureDatasetSRS;
     436         327 :     if (pszFeatureDataset)
     437             :     {
     438             :         {
     439           7 :             FileGDBTable oTable;
     440           7 :             if (!oTable.Open(m_poDS->m_osGDBItemsFilename.c_str(), false))
     441           0 :                 return false;
     442             : 
     443           7 :             FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
     444           7 :             FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
     445           7 :             FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
     446             : 
     447          22 :             for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
     448             :                  ++iCurFeat)
     449             :             {
     450          19 :                 iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
     451          19 :                 if (iCurFeat < 0)
     452           0 :                     break;
     453          19 :                 const auto psName = oTable.GetFieldValue(iName);
     454          19 :                 if (psName && strcmp(psName->String, pszFeatureDataset) == 0)
     455             :                 {
     456           4 :                     const auto psDefinition = oTable.GetFieldValue(iDefinition);
     457           4 :                     if (psDefinition)
     458             :                     {
     459           4 :                         osFeatureDatasetDef = psDefinition->String;
     460             :                     }
     461             :                     else
     462             :                     {
     463           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
     464             :                                  "Feature dataset found, but no definition");
     465           0 :                         return false;
     466             :                     }
     467             : 
     468           4 :                     const auto psUUID = oTable.GetFieldValue(iUUID);
     469           4 :                     if (psUUID == nullptr)
     470             :                     {
     471           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
     472             :                                  "Feature dataset found, but no UUID");
     473           0 :                         return false;
     474             :                     }
     475             : 
     476           4 :                     m_osFeatureDatasetGUID = psUUID->String;
     477           4 :                     break;
     478             :                 }
     479             :             }
     480             :         }
     481             :         CPLXMLNode *psParentTree =
     482           7 :             CPLParseXMLString(osFeatureDatasetDef.c_str());
     483           7 :         if (psParentTree != nullptr)
     484             :         {
     485           4 :             CPLStripXMLNamespace(psParentTree, nullptr, TRUE);
     486             :             CPLXMLNode *psParentInfo =
     487           4 :                 CPLSearchXMLNode(psParentTree, "=DEFeatureDataset");
     488           4 :             if (psParentInfo != nullptr)
     489             :             {
     490           4 :                 poFeatureDatasetSRS = m_poDS->BuildSRS(psParentInfo);
     491             :             }
     492           4 :             CPLDestroyXMLNode(psParentTree);
     493             :         }
     494             :     }
     495             : 
     496         327 :     m_poFeatureDefn =
     497         327 :         new OGROpenFileGDBFeatureDefn(this, m_osName.c_str(), true);
     498         327 :     SetDescription(m_poFeatureDefn->GetName());
     499         327 :     m_poFeatureDefn->SetGeomType(wkbNone);
     500         327 :     m_poFeatureDefn->Reference();
     501             : 
     502         327 :     m_osThisGUID = OFGDBGenerateUUID();
     503             : 
     504         327 :     m_bValidLayerDefn = true;
     505         327 :     m_bEditable = true;
     506         327 :     m_bRegisteredTable = false;
     507         327 :     m_bTimeInUTC = CPLTestBool(
     508             :         m_aosCreationOptions.FetchNameValueDef("TIME_IN_UTC", "YES"));
     509             : 
     510         327 :     int nTablxOffsetSize = 5;
     511         327 :     bool bTextUTF16 = false;
     512             :     const char *pszConfigurationKeyword =
     513         327 :         m_aosCreationOptions.FetchNameValue("CONFIGURATION_KEYWORD");
     514         327 :     if (pszConfigurationKeyword)
     515             :     {
     516           1 :         if (EQUAL(pszConfigurationKeyword, "MAX_FILE_SIZE_4GB"))
     517             :         {
     518           0 :             m_osConfigurationKeyword = "MAX_FILE_SIZE_4GB";
     519           0 :             nTablxOffsetSize = 4;
     520             :         }
     521           1 :         else if (EQUAL(pszConfigurationKeyword, "MAX_FILE_SIZE_256TB"))
     522             :         {
     523           0 :             m_osConfigurationKeyword = "MAX_FILE_SIZE_256TB";
     524           0 :             nTablxOffsetSize = 6;
     525             :         }
     526           1 :         else if (EQUAL(pszConfigurationKeyword, "TEXT_UTF16"))
     527             :         {
     528           1 :             m_osConfigurationKeyword = "TEXT_UTF16";
     529           1 :             bTextUTF16 = true;
     530             :         }
     531           0 :         else if (!EQUAL(pszConfigurationKeyword, "DEFAULTS"))
     532             :         {
     533           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     534             :                      "Unsupported value for CONFIGURATION_KEYWORD: %s",
     535             :                      pszConfigurationKeyword);
     536           0 :             return false;
     537             :         }
     538             :     }
     539             : 
     540         327 :     m_osPath = '\\';
     541         327 :     if (pszFeatureDataset)
     542             :     {
     543           7 :         m_osPath += pszFeatureDataset;
     544           7 :         m_osPath += '\\';
     545             :     }
     546         327 :     m_osPath += m_osName;
     547             : 
     548             :     const char *pszDocumentation =
     549         327 :         m_aosCreationOptions.FetchNameValue("DOCUMENTATION");
     550         327 :     if (pszDocumentation)
     551           1 :         m_osDocumentation = pszDocumentation;
     552             : 
     553         327 :     const bool bGeomTypeHasZ = CPL_TO_BOOL(OGR_GT_HasZ(m_eGeomType));
     554         327 :     const bool bGeomTypeHasM = CPL_TO_BOOL(OGR_GT_HasM(m_eGeomType));
     555             : 
     556         327 :     m_poLyrTable = new FileGDBTable();
     557         327 :     if (!m_poLyrTable->Create(m_osGDBFilename.c_str(), nTablxOffsetSize,
     558             :                               eTableGeomType, bGeomTypeHasZ, bGeomTypeHasM))
     559             :     {
     560           0 :         Close();
     561           0 :         return false;
     562             :     }
     563         327 :     if (bTextUTF16)
     564           1 :         m_poLyrTable->SetTextUTF16();
     565             : 
     566             :     // To be able to test this unusual situation of having an attribute field
     567             :     // before the geometry field
     568         327 :     if (CPLTestBool(CPLGetConfigOption(
     569             :             "OPENFILEGDB_CREATE_FIELD_BEFORE_GEOMETRY", "NO")))
     570             :     {
     571           4 :         OGRFieldDefn oField("field_before_geom", OFTString);
     572           2 :         m_poLyrTable->CreateField(std::make_unique<FileGDBField>(
     573           4 :             oField.GetNameRef(), std::string(), FGFT_STRING,
     574           0 :             /* bNullable = */ true,
     575           0 :             /* bRequired = */ true,
     576           2 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD));
     577           2 :         m_poFeatureDefn->AddFieldDefn(&oField);
     578             :     }
     579             : 
     580         327 :     if (m_eGeomType != wkbNone && poSrcGeomFieldDefn)
     581             :     {
     582         240 :         const auto poSRS = poSrcGeomFieldDefn->GetSpatialRef();
     583             : 
     584             :         auto poGeomFieldDefn = std::make_unique<OGROpenFileGDBGeomFieldDefn>(
     585             :             this,
     586           0 :             m_aosCreationOptions.FetchNameValueDef("GEOMETRY_NAME", "SHAPE"),
     587         240 :             m_eGeomType);
     588         240 :         poGeomFieldDefn->SetNullable(
     589         240 :             CPLTestBool(m_aosCreationOptions.FetchNameValueDef(
     590             :                 "GEOMETRY_NULLABLE", "YES")));
     591             : 
     592         240 :         if (poSRS)
     593             :         {
     594          54 :             const char *const apszOptions[] = {
     595             :                 "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr};
     596          56 :             if (poFeatureDatasetSRS &&
     597          56 :                 !poSRS->IsSame(poFeatureDatasetSRS.get(), apszOptions))
     598             :             {
     599           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     600             :                          "Layer CRS does not match feature dataset CRS");
     601           1 :                 return false;
     602             :             }
     603             : 
     604             :             auto poSRSClone =
     605         106 :                 OGRSpatialReferenceRefCountedPtr::makeClone(poSRS);
     606          53 :             poGeomFieldDefn->SetSpatialRef(poSRSClone.get());
     607             :         }
     608         186 :         else if (poFeatureDatasetSRS)
     609             :         {
     610           1 :             poGeomFieldDefn->SetSpatialRef(poFeatureDatasetSRS.get());
     611             :         }
     612             : 
     613         239 :         std::string osWKT;
     614         239 :         if (poSRS)
     615             :         {
     616          53 :             const char *const apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
     617             :             char *pszWKT;
     618          53 :             poSRS->exportToWkt(&pszWKT, apszOptions);
     619          53 :             osWKT = pszWKT;
     620          53 :             CPLFree(pszWKT);
     621             :         }
     622             :         else
     623             :         {
     624         186 :             osWKT = "{B286C06B-0879-11D2-AACA-00C04FA33C20}";
     625             :         }
     626             : 
     627             :         const auto oCoordPrec = GDBGridSettingsFromOGR(
     628         239 :             poSrcGeomFieldDefn, m_aosCreationOptions.List());
     629         239 :         poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec);
     630             :         const auto &oGridsOptions =
     631         239 :             oCoordPrec.oFormatSpecificOptions.find("FileGeodatabase")->second;
     632             :         const double dfXOrigin =
     633         239 :             CPLAtof(oGridsOptions.FetchNameValue("XOrigin"));
     634             :         const double dfYOrigin =
     635         239 :             CPLAtof(oGridsOptions.FetchNameValue("YOrigin"));
     636             :         const double dfXYScale =
     637         239 :             CPLAtof(oGridsOptions.FetchNameValue("XYScale"));
     638             :         const double dfXYTolerance =
     639         239 :             CPLAtof(oGridsOptions.FetchNameValue("XYTolerance"));
     640             :         const double dfZOrigin =
     641         239 :             CPLAtof(oGridsOptions.FetchNameValue("ZOrigin"));
     642         239 :         const double dfZScale = CPLAtof(oGridsOptions.FetchNameValue("ZScale"));
     643             :         const double dfZTolerance =
     644         239 :             CPLAtof(oGridsOptions.FetchNameValue("ZTolerance"));
     645             :         const double dfMOrigin =
     646         239 :             CPLAtof(oGridsOptions.FetchNameValue("MOrigin"));
     647         239 :         const double dfMScale = CPLAtof(oGridsOptions.FetchNameValue("MScale"));
     648             :         const double dfMTolerance =
     649         239 :             CPLAtof(oGridsOptions.FetchNameValue("MTolerance"));
     650             : 
     651         239 :         if (!m_poDS->GetExistingSpatialRef(
     652             :                 osWKT, dfXOrigin, dfYOrigin, dfXYScale, dfZOrigin, dfZScale,
     653             :                 dfMOrigin, dfMScale, dfXYTolerance, dfZTolerance, dfMTolerance))
     654             :         {
     655         184 :             m_poDS->AddNewSpatialRef(osWKT, dfXOrigin, dfYOrigin, dfXYScale,
     656             :                                      dfZOrigin, dfZScale, dfMOrigin, dfMScale,
     657             :                                      dfXYTolerance, dfZTolerance, dfMTolerance);
     658             :         }
     659             : 
     660             :         // Will be patched later
     661         239 :         constexpr double dfSpatialGridResolution = 0;
     662             :         auto poTableGeomField = std::make_unique<FileGDBGeomField>(
     663         478 :             poGeomFieldDefn->GetNameRef(),
     664           0 :             std::string(),  // alias
     665         478 :             CPL_TO_BOOL(poGeomFieldDefn->IsNullable()), osWKT, dfXOrigin,
     666             :             dfYOrigin, dfXYScale, dfXYTolerance,
     667         717 :             std::vector<double>{dfSpatialGridResolution});
     668         239 :         poTableGeomField->SetZOriginScaleTolerance(dfZOrigin, dfZScale,
     669             :                                                    dfZTolerance);
     670         239 :         poTableGeomField->SetMOriginScaleTolerance(dfMOrigin, dfMScale,
     671             :                                                    dfMTolerance);
     672             : 
     673         239 :         if (!m_poLyrTable->CreateField(std::move(poTableGeomField)))
     674             :         {
     675           0 :             Close();
     676           0 :             return false;
     677             :         }
     678             : 
     679         239 :         m_iGeomFieldIdx = m_poLyrTable->GetGeomFieldIdx();
     680         239 :         m_poGeomConverter.reset(FileGDBOGRGeometryConverter::BuildConverter(
     681         239 :             m_poLyrTable->GetGeomField()));
     682             : 
     683         239 :         m_poFeatureDefn->AddGeomFieldDefn(std::move(poGeomFieldDefn));
     684             :     }
     685             : 
     686             :     const std::string osFIDName =
     687         652 :         m_aosCreationOptions.FetchNameValueDef("FID", "OBJECTID");
     688         326 :     if (!m_poLyrTable->CreateField(std::make_unique<FileGDBField>(
     689         326 :             osFIDName, std::string(), FGFT_OBJECTID,
     690           0 :             /* bNullable = */ false,
     691           0 :             /* bRequired = */ true,
     692         652 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)))
     693             :     {
     694           0 :         Close();
     695           0 :         return false;
     696             :     }
     697             : 
     698             :     const bool bCreateShapeLength =
     699         440 :         (eTableGeomType == FGTGT_LINE || eTableGeomType == FGTGT_POLYGON) &&
     700         114 :         CPLTestBool(m_aosCreationOptions.FetchNameValueDef(
     701         326 :             "CREATE_SHAPE_AREA_AND_LENGTH_FIELDS", "NO"));
     702             :     // Setting a non-default value doesn't work
     703         326 :     const char *pszLengthFieldName = m_aosCreationOptions.FetchNameValueDef(
     704             :         "LENGTH_FIELD_NAME", "Shape_Length");
     705             : 
     706             :     const bool bCreateShapeArea =
     707         393 :         eTableGeomType == FGTGT_POLYGON &&
     708          67 :         CPLTestBool(m_aosCreationOptions.FetchNameValueDef(
     709         326 :             "CREATE_SHAPE_AREA_AND_LENGTH_FIELDS", "NO"));
     710             :     // Setting a non-default value doesn't work
     711             :     const char *pszAreaFieldName =
     712         326 :         m_aosCreationOptions.FetchNameValueDef("AREA_FIELD_NAME", "Shape_Area");
     713             : 
     714         326 :     m_poFeatureDefn->Seal(/* bSealFields = */ true);
     715             : 
     716         326 :     if (bCreateShapeArea)
     717             :     {
     718           4 :         OGRFieldDefn oField(pszAreaFieldName, OFTReal);
     719           4 :         oField.SetDefault("FILEGEODATABASE_SHAPE_AREA");
     720           4 :         if (CreateField(&oField, false) != OGRERR_NONE)
     721             :         {
     722           0 :             Close();
     723           0 :             return false;
     724             :         }
     725             :     }
     726         326 :     if (bCreateShapeLength)
     727             :     {
     728           7 :         OGRFieldDefn oField(pszLengthFieldName, OFTReal);
     729           7 :         oField.SetDefault("FILEGEODATABASE_SHAPE_LENGTH");
     730           7 :         if (CreateField(&oField, false) != OGRERR_NONE)
     731             :         {
     732           0 :             Close();
     733           0 :             return false;
     734             :         }
     735             :     }
     736             : 
     737         326 :     m_poLyrTable->CreateIndex("FDO_OBJECTID", osFIDName);
     738             : 
     739             :     // Just to imitate the FileGDB SDK which register the index on the
     740             :     // geometry column after the OBJECTID one, but the OBJECTID column is the
     741             :     // first one in .gdbtable
     742         326 :     if (m_iGeomFieldIdx >= 0)
     743         478 :         m_poLyrTable->CreateIndex(
     744         239 :             "FDO_SHAPE", m_poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef());
     745             : 
     746         326 :     if (!m_poDS->RegisterLayerInSystemCatalog(m_osName))
     747             :     {
     748           0 :         Close();
     749           0 :         return false;
     750             :     }
     751             : 
     752         329 :     if (pszFeatureDataset != nullptr && m_osFeatureDatasetGUID.empty() &&
     753           3 :         !CreateFeatureDataset(pszFeatureDataset))
     754             :     {
     755           0 :         Close();
     756           0 :         return false;
     757             :     }
     758             : 
     759         326 :     RefreshXMLDefinitionInMemory();
     760             : 
     761         326 :     return true;
     762             : }
     763             : 
     764             : /************************************************************************/
     765             : /*                      CreateXMLFieldDefinition()                      */
     766             : /************************************************************************/
     767             : 
     768        4098 : static CPLXMLNode *CreateXMLFieldDefinition(const OGRFieldDefn *poFieldDefn,
     769             :                                             const FileGDBField *poGDBFieldDefn,
     770             :                                             bool bArcGISPro32OrLater)
     771             : {
     772             :     auto GPFieldInfoEx =
     773        4098 :         CPLCreateXMLNode(nullptr, CXT_Element, "GPFieldInfoEx");
     774        4098 :     CPLAddXMLAttributeAndValue(GPFieldInfoEx, "xsi:type",
     775             :                                "typens:GPFieldInfoEx");
     776        4098 :     CPLCreateXMLElementAndValue(GPFieldInfoEx, "Name",
     777        4098 :                                 poGDBFieldDefn->GetName().c_str());
     778        4098 :     if (!poGDBFieldDefn->GetAlias().empty())
     779             :     {
     780          18 :         CPLCreateXMLElementAndValue(GPFieldInfoEx, "AliasName",
     781          18 :                                     poGDBFieldDefn->GetAlias().c_str());
     782             :     }
     783        4098 :     const auto *psDefault = poGDBFieldDefn->GetDefault();
     784        4098 :     if (!OGR_RawField_IsNull(psDefault) && !OGR_RawField_IsUnset(psDefault))
     785             :     {
     786         228 :         if (poGDBFieldDefn->GetType() == FGFT_STRING)
     787             :         {
     788         122 :             auto psDefaultValue = CPLCreateXMLElementAndValue(
     789          61 :                 GPFieldInfoEx, "DefaultValueString", psDefault->String);
     790          61 :             if (!bArcGISPro32OrLater)
     791             :             {
     792          61 :                 CPLAddXMLAttributeAndValue(
     793             :                     psDefaultValue, "xmlns:typens",
     794             :                     "http://www.esri.com/schemas/ArcGIS/10.3");
     795             :             }
     796             :         }
     797         167 :         else if (poGDBFieldDefn->GetType() == FGFT_INT32)
     798             :         {
     799          47 :             auto psDefaultValue = CPLCreateXMLElementAndValue(
     800             :                 GPFieldInfoEx, "DefaultValue",
     801          47 :                 CPLSPrintf("%d", psDefault->Integer));
     802          47 :             CPLAddXMLAttributeAndValue(psDefaultValue, "xsi:type", "xs:int");
     803             :         }
     804         120 :         else if (poGDBFieldDefn->GetType() == FGFT_FLOAT64)
     805             :         {
     806          27 :             auto psDefaultValue = CPLCreateXMLElementAndValue(
     807             :                 GPFieldInfoEx, "DefaultValueNumeric",
     808          27 :                 CPLSPrintf("%.17g", psDefault->Real));
     809          27 :             if (!bArcGISPro32OrLater)
     810             :             {
     811          27 :                 CPLAddXMLAttributeAndValue(
     812             :                     psDefaultValue, "xmlns:typens",
     813             :                     "http://www.esri.com/schemas/ArcGIS/10.3");
     814             :             }
     815             :         }
     816          93 :         else if (poGDBFieldDefn->GetType() == FGFT_INT64)
     817             :         {
     818           1 :             CPLCreateXMLElementAndValue(
     819             :                 GPFieldInfoEx, "DefaultValueInteger",
     820           1 :                 CPLSPrintf(CPL_FRMT_GIB, psDefault->Integer64));
     821             :         }
     822         172 :         else if (poGDBFieldDefn->GetType() == FGFT_DATETIME ||
     823          80 :                  poGDBFieldDefn->GetType() == FGFT_DATE)
     824             :         {
     825          18 :             CPLCreateXMLElementAndValue(
     826             :                 GPFieldInfoEx, "DefaultValueNumeric",
     827             :                 CPLSPrintf("%.17g", FileGDBOGRDateToDoubleDate(
     828             :                                         psDefault, /* bConvertToUTC = */ true,
     829          18 :                                         poGDBFieldDefn->IsHighPrecision())));
     830             :         }
     831          74 :         else if (poGDBFieldDefn->GetType() == FGFT_TIME)
     832             :         {
     833           4 :             CPLCreateXMLElementAndValue(
     834             :                 GPFieldInfoEx, "DefaultValueNumeric",
     835             :                 CPLSPrintf("%.0f", FileGDBOGRTimeToDoubleTime(psDefault)));
     836             :         }
     837          70 :         else if (poGDBFieldDefn->GetType() == FGFT_DATETIME_WITH_OFFSET)
     838             :         {
     839             :             /*
     840             :              <DefaultValueTimestampOffset xsi:type="typens:TimestampOffset">
     841             :                 <Timestamp>2023-02-01T04:05:06</Timestamp>
     842             :                 <HoursOffset>6</HoursOffset>
     843             :                 <MinutesOffset>0</MinutesOffset>
     844             :               </DefaultValueTimestampOffset>
     845             :             */
     846          10 :             auto psDefaultValue = CPLCreateXMLNode(
     847             :                 GPFieldInfoEx, CXT_Element, "DefaultValueTimestampOffset");
     848          10 :             CPLAddXMLAttributeAndValue(psDefaultValue, "xsi:type",
     849             :                                        "typens:TimestampOffset");
     850          10 :             CPLCreateXMLElementAndValue(
     851             :                 psDefaultValue, "Timestamp",
     852             :                 CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02d",
     853          10 :                            psDefault->Date.Year, psDefault->Date.Month,
     854          10 :                            psDefault->Date.Day, psDefault->Date.Hour,
     855          10 :                            psDefault->Date.Minute,
     856          10 :                            static_cast<int>(psDefault->Date.Second)));
     857          10 :             if (psDefault->Date.TZFlag > 1)
     858             :             {
     859           2 :                 const int nOffsetInMin = (psDefault->Date.TZFlag - 100) * 15;
     860           2 :                 CPLCreateXMLElementAndValue(
     861             :                     psDefaultValue, "HoursOffset",
     862             :                     CPLSPrintf("%d", nOffsetInMin / 60));
     863           2 :                 CPLCreateXMLElementAndValue(
     864             :                     psDefaultValue, "MinutesOffset",
     865           2 :                     CPLSPrintf("%d", std::abs(nOffsetInMin) % 60));
     866             :             }
     867             :         }
     868             :     }
     869        4098 :     const char *pszFieldType = "";
     870        4098 :     CPL_IGNORE_RET_VAL(pszFieldType);  // Make CSA happy
     871        4098 :     int nLength = 0;
     872        4098 :     switch (poGDBFieldDefn->GetType())
     873             :     {
     874           0 :         case FGFT_UNDEFINED:
     875           0 :             CPLAssert(false);
     876             :             break;
     877         501 :         case FGFT_INT16:
     878         501 :             nLength = 2;
     879         501 :             pszFieldType = "esriFieldTypeSmallInteger";
     880         501 :             break;
     881        1033 :         case FGFT_INT32:
     882        1033 :             nLength = 4;
     883        1033 :             pszFieldType = "esriFieldTypeInteger";
     884        1033 :             break;
     885         378 :         case FGFT_FLOAT32:
     886         378 :             nLength = 4;
     887         378 :             pszFieldType = "esriFieldTypeSingle";
     888         378 :             break;
     889         425 :         case FGFT_FLOAT64:
     890         425 :             nLength = 8;
     891         425 :             pszFieldType = "esriFieldTypeDouble";
     892         425 :             break;
     893         803 :         case FGFT_STRING:
     894         803 :             nLength = poGDBFieldDefn->GetMaxWidth();
     895         803 :             pszFieldType = "esriFieldTypeString";
     896         803 :             break;
     897         306 :         case FGFT_DATETIME:
     898         306 :             nLength = 8;
     899         306 :             pszFieldType = "esriFieldTypeDate";
     900         306 :             break;
     901           0 :         case FGFT_OBJECTID:
     902           0 :             pszFieldType = "esriFieldTypeOID";
     903           0 :             break;  // shouldn't happen
     904           0 :         case FGFT_GEOMETRY:
     905           0 :             pszFieldType = "esriFieldTypeGeometry";
     906           0 :             break;  // shouldn't happen
     907         244 :         case FGFT_BINARY:
     908         244 :             pszFieldType = "esriFieldTypeBlob";
     909         244 :             break;
     910           0 :         case FGFT_RASTER:
     911           0 :             pszFieldType = "esriFieldTypeRaster";
     912           0 :             break;
     913         206 :         case FGFT_GUID:
     914         206 :             pszFieldType = "esriFieldTypeGUID";
     915         206 :             break;
     916           3 :         case FGFT_GLOBALID:
     917           3 :             pszFieldType = "esriFieldTypeGlobalID";
     918           3 :             break;
     919         174 :         case FGFT_XML:
     920         174 :             pszFieldType = "esriFieldTypeXML";
     921         174 :             break;
     922           5 :         case FGFT_INT64:
     923           5 :             nLength = 8;
     924           5 :             pszFieldType = "esriFieldTypeBigInteger";
     925           5 :             break;
     926           6 :         case FGFT_DATE:
     927           6 :             nLength = 8;
     928           6 :             pszFieldType = "esriFieldTypeDateOnly";
     929           6 :             break;
     930           4 :         case FGFT_TIME:
     931           4 :             nLength = 8;
     932           4 :             pszFieldType = "esriFieldTypeTimeOnly";
     933           4 :             break;
     934          10 :         case FGFT_DATETIME_WITH_OFFSET:
     935          10 :             nLength = 8 + 2;
     936          10 :             pszFieldType = "esriFieldTypeTimestampOffset";
     937          10 :             break;
     938             :     }
     939             :     auto psFieldType =
     940        4098 :         CPLCreateXMLElementAndValue(GPFieldInfoEx, "FieldType", pszFieldType);
     941        4098 :     if (!bArcGISPro32OrLater)
     942             :     {
     943        4065 :         CPLAddXMLAttributeAndValue(psFieldType, "xmlns:typens",
     944             :                                    "http://www.esri.com/schemas/ArcGIS/10.3");
     945             :     }
     946        4098 :     if (poGDBFieldDefn->IsNullable())
     947             :     {
     948        3893 :         CPLCreateXMLElementAndValue(GPFieldInfoEx, "IsNullable", "true");
     949             :     }
     950        4098 :     if (poGDBFieldDefn->IsRequired())
     951             :     {
     952          51 :         CPLCreateXMLElementAndValue(GPFieldInfoEx, "Required", "true");
     953             :     }
     954        4098 :     if (!poGDBFieldDefn->IsEditable())
     955             :     {
     956          47 :         CPLCreateXMLElementAndValue(GPFieldInfoEx, "Editable", "false");
     957             :     }
     958        4098 :     if (poGDBFieldDefn->IsHighPrecision())
     959             :     {
     960           0 :         CPLCreateXMLElementAndValue(GPFieldInfoEx, "HighPrecision", "true");
     961             :     }
     962        4098 :     CPLCreateXMLElementAndValue(GPFieldInfoEx, "Length",
     963             :                                 CPLSPrintf("%d", nLength));
     964        4098 :     CPLCreateXMLElementAndValue(GPFieldInfoEx, "Precision", "0");
     965        4098 :     CPLCreateXMLElementAndValue(GPFieldInfoEx, "Scale", "0");
     966        4098 :     if (!poFieldDefn->GetDomainName().empty())
     967             :     {
     968          13 :         CPLCreateXMLElementAndValue(GPFieldInfoEx, "DomainName",
     969          13 :                                     poFieldDefn->GetDomainName().c_str());
     970             :     }
     971        4098 :     return GPFieldInfoEx;
     972             : }
     973             : 
     974             : /************************************************************************/
     975             : /*                             GetDefault()                             */
     976             : /************************************************************************/
     977             : 
     978         844 : static bool GetDefault(const OGRFieldDefn *poField, FileGDBFieldType eType,
     979             :                        OGRField &sDefault, std::string &osDefaultVal,
     980             :                        bool bApproxOK)
     981             : {
     982         844 :     sDefault = FileGDBField::UNSET_FIELD;
     983         844 :     const char *pszDefault = poField->GetDefault();
     984         844 :     if (pszDefault != nullptr && !poField->IsDefaultDriverSpecific())
     985             :     {
     986          79 :         if (eType == FGFT_STRING)
     987             :         {
     988          16 :             osDefaultVal = pszDefault;
     989          16 :             if (osDefaultVal[0] == '\'' && osDefaultVal.back() == '\'')
     990             :             {
     991          16 :                 osDefaultVal = osDefaultVal.substr(1);
     992          16 :                 osDefaultVal.pop_back();
     993             :                 char *pszTmp =
     994          16 :                     CPLUnescapeString(osDefaultVal.c_str(), nullptr, CPLES_SQL);
     995          16 :                 osDefaultVal = pszTmp;
     996          16 :                 CPLFree(pszTmp);
     997             :             }
     998          16 :             sDefault.String = &osDefaultVal[0];
     999             :         }
    1000          63 :         else if (eType == FGFT_INT16 || eType == FGFT_INT32)
    1001          16 :             sDefault.Integer = atoi(pszDefault);
    1002          47 :         else if (eType == FGFT_FLOAT32 || eType == FGFT_FLOAT64)
    1003          14 :             sDefault.Real = CPLAtof(pszDefault);
    1004          33 :         else if (eType == FGFT_DATETIME || eType == FGFT_DATE ||
    1005           5 :                  eType == FGFT_TIME || eType == FGFT_DATETIME_WITH_OFFSET)
    1006             :         {
    1007          32 :             osDefaultVal = pszDefault;
    1008          52 :             if (osDefaultVal == "CURRENT_TIMESTAMP" ||
    1009          52 :                 osDefaultVal == "CURRENT_TIME" ||
    1010          20 :                 osDefaultVal == "CURRENT_DATE")
    1011             :             {
    1012          12 :                 CPLError(bApproxOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
    1013             :                          "%s is not supported as a default value in File "
    1014             :                          "Geodatabase",
    1015             :                          osDefaultVal.c_str());
    1016          12 :                 return bApproxOK;
    1017             :             }
    1018          20 :             if (osDefaultVal[0] == '\'' && osDefaultVal.back() == '\'')
    1019             :             {
    1020          20 :                 osDefaultVal = osDefaultVal.substr(1);
    1021          20 :                 osDefaultVal.pop_back();
    1022             :                 char *pszTmp =
    1023          20 :                     CPLUnescapeString(osDefaultVal.c_str(), nullptr, CPLES_SQL);
    1024          20 :                 osDefaultVal = pszTmp;
    1025          20 :                 CPLFree(pszTmp);
    1026             :             }
    1027          20 :             if (!OGRParseDate(osDefaultVal.c_str(), &sDefault, 0))
    1028             :             {
    1029           6 :                 CPLError(bApproxOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
    1030             :                          "Cannot parse %s as a date time",
    1031             :                          osDefaultVal.c_str());
    1032           6 :                 return bApproxOK;
    1033          14 :             }
    1034             :         }
    1035           1 :         else if (eType == FGFT_INT64)
    1036           1 :             sDefault.Integer64 = CPLAtoGIntBig(pszDefault);
    1037             :     }
    1038         826 :     return true;
    1039             : }
    1040             : 
    1041             : /************************************************************************/
    1042             : /*                          GetGDBFieldType()                           */
    1043             : /************************************************************************/
    1044             : 
    1045         702 : static FileGDBFieldType GetGDBFieldType(const OGRFieldDefn *poField,
    1046             :                                         bool bArcGISPro32OrLater)
    1047             : {
    1048         702 :     FileGDBFieldType eType = FGFT_UNDEFINED;
    1049         702 :     switch (poField->GetType())
    1050             :     {
    1051         230 :         case OFTInteger:
    1052         230 :             eType =
    1053         230 :                 poField->GetSubType() == OFSTInt16 ? FGFT_INT16 : FGFT_INT32;
    1054         230 :             break;
    1055         121 :         case OFTReal:
    1056         121 :             eType = poField->GetSubType() == OFSTFloat32 ? FGFT_FLOAT32
    1057             :                                                          : FGFT_FLOAT64;
    1058         121 :             break;
    1059           9 :         case OFTInteger64:
    1060           9 :             eType = bArcGISPro32OrLater ? FGFT_INT64 : FGFT_FLOAT64;
    1061           9 :             break;
    1062         174 :         case OFTString:
    1063             :         case OFTWideString:
    1064             :         case OFTStringList:
    1065             :         case OFTWideStringList:
    1066             :         case OFTIntegerList:
    1067             :         case OFTInteger64List:
    1068             :         case OFTRealList:
    1069         174 :             eType = FGFT_STRING;
    1070         174 :             break;
    1071          71 :         case OFTBinary:
    1072          71 :             eType = FGFT_BINARY;
    1073          71 :             break;
    1074          16 :         case OFTDate:
    1075          16 :             eType = bArcGISPro32OrLater ? FGFT_DATE : FGFT_DATETIME;
    1076          16 :             break;
    1077           2 :         case OFTTime:
    1078           2 :             eType = bArcGISPro32OrLater ? FGFT_TIME : FGFT_DATETIME;
    1079           2 :             break;
    1080          79 :         case OFTDateTime:
    1081          79 :             eType =
    1082          79 :                 bArcGISPro32OrLater ? FGFT_DATETIME_WITH_OFFSET : FGFT_DATETIME;
    1083          79 :             break;
    1084             :     }
    1085         702 :     return eType;
    1086             : }
    1087             : 
    1088             : /************************************************************************/
    1089             : /*                       GetGPFieldInfoExsNode()                        */
    1090             : /************************************************************************/
    1091             : 
    1092          32 : static CPLXMLNode *GetGPFieldInfoExsNode(CPLXMLNode *psParent)
    1093             : {
    1094          32 :     CPLXMLNode *psInfo = CPLSearchXMLNode(psParent, "=DEFeatureClassInfo");
    1095          32 :     if (psInfo == nullptr)
    1096          32 :         psInfo = CPLSearchXMLNode(psParent, "=typens:DEFeatureClassInfo");
    1097          32 :     if (psInfo == nullptr)
    1098           5 :         psInfo = CPLSearchXMLNode(psParent, "=DETableInfo");
    1099          32 :     if (psInfo == nullptr)
    1100           5 :         psInfo = CPLSearchXMLNode(psParent, "=typens:DETableInfo");
    1101          32 :     if (psInfo != nullptr)
    1102             :     {
    1103          32 :         return CPLGetXMLNode(psInfo, "GPFieldInfoExs");
    1104             :     }
    1105           0 :     return nullptr;
    1106             : }
    1107             : 
    1108             : /************************************************************************/
    1109             : /*                       GetLaunderedFieldName()                        */
    1110             : /************************************************************************/
    1111             : 
    1112             : std::string
    1113         844 : OGROpenFileGDBLayer::GetLaunderedFieldName(const std::string &osNameOri) const
    1114             : {
    1115        1688 :     std::wstring osName = LaunderName(StringToWString(osNameOri));
    1116         844 :     osName = EscapeReservedKeywords(osName);
    1117             : 
    1118             :     /* Truncate to 64 characters */
    1119         844 :     constexpr size_t FIELD_NAME_MAX_SIZE = 64;
    1120         844 :     if (osName.size() > FIELD_NAME_MAX_SIZE)
    1121           2 :         osName.resize(FIELD_NAME_MAX_SIZE);
    1122             : 
    1123             :     /* Ensures uniqueness of field name */
    1124         844 :     int numRenames = 1;
    1125        1696 :     while ((m_poFeatureDefn->GetFieldIndex(WStringToString(osName).c_str()) >=
    1126        2544 :             0) &&
    1127             :            (numRenames < 10))
    1128             :     {
    1129          12 :         osName = StringToWString(CPLSPrintf(
    1130             :             "%s_%d",
    1131           8 :             WStringToString(osName.substr(0, FIELD_NAME_MAX_SIZE - 2)).c_str(),
    1132           4 :             numRenames));
    1133           4 :         numRenames++;
    1134             :     }
    1135        1688 :     while ((m_poFeatureDefn->GetFieldIndex(WStringToString(osName).c_str()) >=
    1136        2532 :             0) &&
    1137             :            (numRenames < 100))
    1138             :     {
    1139           0 :         osName = StringToWString(CPLSPrintf(
    1140             :             "%s_%d",
    1141           0 :             WStringToString(osName.substr(0, FIELD_NAME_MAX_SIZE - 3)).c_str(),
    1142           0 :             numRenames));
    1143           0 :         numRenames++;
    1144             :     }
    1145             : 
    1146        1688 :     return WStringToString(osName);
    1147             : }
    1148             : 
    1149             : /************************************************************************/
    1150             : /*                            CreateField()                             */
    1151             : /************************************************************************/
    1152             : 
    1153         840 : OGRErr OGROpenFileGDBLayer::CreateField(const OGRFieldDefn *poFieldIn,
    1154             :                                         int bApproxOK)
    1155             : {
    1156         840 :     if (!m_bEditable)
    1157           0 :         return OGRERR_FAILURE;
    1158             : 
    1159         840 :     if (!BuildLayerDefinition())
    1160           0 :         return OGRERR_FAILURE;
    1161             : 
    1162         843 :     if (m_poDS->IsInTransaction() &&
    1163           3 :         ((!m_bHasCreatedBackupForTransaction && !BeginEmulatedTransaction()) ||
    1164           3 :          !m_poDS->BackupSystemTablesForTransaction()))
    1165             :     {
    1166           0 :         return OGRERR_FAILURE;
    1167             :     }
    1168             : 
    1169             :     /* Clean field names */
    1170        1680 :     OGRFieldDefn oField(poFieldIn);
    1171         840 :     OGRFieldDefn *poField = &oField;
    1172             : 
    1173         840 :     if (poField->GetType() == OFTInteger64 && !m_bArcGISPro32OrLater)
    1174             :     {
    1175           7 :         CPLError(CE_Warning, CPLE_AppDefined,
    1176             :                  "Field %s of type Integer64 will be written as a Float64. "
    1177             :                  "To get Integer64, use layer creation option "
    1178             :                  "TARGET_ARCGIS_VERSION=ARCGIS_PRO_3_2_OR_LATER",
    1179             :                  poField->GetNameRef());
    1180             :     }
    1181         833 :     else if (poField->GetType() == OFTDate && !m_bArcGISPro32OrLater)
    1182             :     {
    1183          14 :         CPLError(CE_Warning, CPLE_AppDefined,
    1184             :                  "Field %s of type Date will be written as a DateTime. "
    1185             :                  "To get DateTime, use layer creation option "
    1186             :                  "TARGET_ARCGIS_VERSION=ARCGIS_PRO_3_2_OR_LATER",
    1187             :                  poField->GetNameRef());
    1188             :     }
    1189         819 :     else if (poField->GetType() == OFTTime && !m_bArcGISPro32OrLater)
    1190             :     {
    1191           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    1192             :                  "Field %s of type Time will be written as a DateTime. "
    1193             :                  "To get DateTime, use layer creation option "
    1194             :                  "TARGET_ARCGIS_VERSION=ARCGIS_PRO_3_2_OR_LATER",
    1195             :                  poField->GetNameRef());
    1196             :     }
    1197             : 
    1198        1680 :     const std::string osFidColumn = GetFIDColumn();
    1199        1680 :     if (!osFidColumn.empty() &&
    1200         840 :         EQUAL(poField->GetNameRef(), osFidColumn.c_str()))
    1201             :     {
    1202           5 :         if (poField->GetType() != OFTInteger &&
    1203           6 :             poField->GetType() != OFTInteger64 &&
    1204             :             // typically a GeoPackage exported with QGIS as a shapefile and
    1205             :             // re-imported See https://github.com/qgis/QGIS/pull/43118
    1206           3 :             !(poField->GetType() == OFTReal && poField->GetWidth() <= 20 &&
    1207           1 :               poField->GetPrecision() == 0))
    1208             :         {
    1209           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1210             :                      "Wrong field type for %s : %d", poField->GetNameRef(),
    1211           0 :                      poField->GetType());
    1212           0 :             return OGRERR_FAILURE;
    1213             :         }
    1214             : 
    1215           3 :         m_iFIDAsRegularColumnIndex = m_poFeatureDefn->GetFieldCount();
    1216           3 :         whileUnsealing(m_poFeatureDefn)->AddFieldDefn(poField);
    1217           3 :         return OGRERR_NONE;
    1218             :     }
    1219             : 
    1220        1674 :     const std::string osFieldNameOri(poField->GetNameRef());
    1221        1674 :     const std::string osFieldName = GetLaunderedFieldName(osFieldNameOri);
    1222         837 :     if (osFieldName != osFieldNameOri)
    1223             :     {
    1224          10 :         if (!bApproxOK ||
    1225           5 :             (m_poFeatureDefn->GetFieldIndex(osFieldName.c_str()) >= 0))
    1226             :         {
    1227           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    1228             :                      "Failed to add field named '%s'", osFieldNameOri.c_str());
    1229           0 :             return OGRERR_FAILURE;
    1230             :         }
    1231           5 :         CPLError(CE_Warning, CPLE_NotSupported,
    1232             :                  "Normalized/laundered field name: '%s' to '%s'",
    1233             :                  osFieldNameOri.c_str(), osFieldName.c_str());
    1234             : 
    1235           5 :         poField->SetName(osFieldName.c_str());
    1236             :     }
    1237             : 
    1238             :     const char *pszColumnTypes =
    1239         837 :         m_aosCreationOptions.FetchNameValue("COLUMN_TYPES");
    1240        1674 :     std::string gdbFieldType;
    1241         837 :     if (pszColumnTypes != nullptr)
    1242             :     {
    1243         470 :         char **papszTokens = CSLTokenizeString2(pszColumnTypes, ",", 0);
    1244             :         const char *pszFieldType =
    1245         470 :             CSLFetchNameValue(papszTokens, poField->GetNameRef());
    1246         470 :         if (pszFieldType != nullptr)
    1247             :         {
    1248             :             OGRFieldType fldtypeCheck;
    1249             :             OGRFieldSubType eSubType;
    1250         142 :             if (GDBToOGRFieldType(pszFieldType, &fldtypeCheck, &eSubType))
    1251             :             {
    1252         142 :                 if (fldtypeCheck != poField->GetType())
    1253             :                 {
    1254           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1255             :                              "Ignoring COLUMN_TYPES=%s=%s : %s not consistent "
    1256             :                              "with OGR data type",
    1257             :                              poField->GetNameRef(), pszFieldType, pszFieldType);
    1258             :                 }
    1259             :                 else
    1260         142 :                     gdbFieldType = pszFieldType;
    1261             :             }
    1262             :             else
    1263           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1264             :                          "Ignoring COLUMN_TYPES=%s=%s : %s not recognized",
    1265             :                          poField->GetNameRef(), pszFieldType, pszFieldType);
    1266             :         }
    1267         470 :         CSLDestroy(papszTokens);
    1268             :     }
    1269             : 
    1270         837 :     FileGDBFieldType eType = FGFT_UNDEFINED;
    1271         837 :     if (!gdbFieldType.empty())
    1272             :     {
    1273         142 :         if (gdbFieldType == "esriFieldTypeSmallInteger")
    1274          34 :             eType = FGFT_INT16;
    1275         108 :         else if (gdbFieldType == "esriFieldTypeInteger")
    1276           0 :             eType = FGFT_INT32;
    1277         108 :         else if (gdbFieldType == "esriFieldTypeBigInteger")
    1278           0 :             eType = FGFT_INT64;
    1279         108 :         else if (gdbFieldType == "esriFieldTypeSingle")
    1280          34 :             eType = FGFT_FLOAT32;
    1281          74 :         else if (gdbFieldType == "esriFieldTypeDouble")
    1282           0 :             eType = FGFT_FLOAT64;
    1283          74 :         else if (gdbFieldType == "esriFieldTypeString")
    1284           0 :             eType = FGFT_STRING;
    1285          74 :         else if (gdbFieldType == "esriFieldTypeDate")
    1286           0 :             eType = FGFT_DATETIME;
    1287          74 :         else if (gdbFieldType == "esriFieldTypeBlob")
    1288           0 :             eType = FGFT_BINARY;
    1289          74 :         else if (gdbFieldType == "esriFieldTypeGUID")
    1290          36 :             eType = FGFT_GUID;
    1291          38 :         else if (gdbFieldType == "esriFieldTypeGlobalID")
    1292           2 :             eType = FGFT_GLOBALID;
    1293          36 :         else if (gdbFieldType == "esriFieldTypeXML")
    1294          36 :             eType = FGFT_XML;
    1295           0 :         else if (gdbFieldType == "esriFieldTypeDateOnly")
    1296           0 :             eType = FGFT_DATE;
    1297           0 :         else if (gdbFieldType == "esriFieldTypeTimeOnly")
    1298           0 :             eType = FGFT_TIME;
    1299           0 :         else if (gdbFieldType == "esriFieldTypeTimestampOffset")
    1300           0 :             eType = FGFT_DATETIME_WITH_OFFSET;
    1301             :         else
    1302             :         {
    1303           0 :             CPLAssert(false);
    1304             :         }
    1305             :     }
    1306             :     else
    1307             :     {
    1308         695 :         eType = GetGDBFieldType(poField, m_bArcGISPro32OrLater);
    1309             :     }
    1310             : 
    1311         837 :     int nWidth = 0;
    1312         837 :     if (eType == FGFT_GLOBALID || eType == FGFT_GUID)
    1313             :     {
    1314          38 :         nWidth = 38;
    1315             :     }
    1316         799 :     else if (poField->GetType() == OFTString)
    1317             :     {
    1318         207 :         nWidth = poField->GetWidth();
    1319         207 :         if (nWidth == 0)
    1320             :         {
    1321             :             // Hard-coded non-zero default string width if the user doesn't
    1322             :             // override it with the below configuration option.
    1323             :             // See comment at declaration of DEFAULT_STRING_WIDTH for more
    1324             :             // details
    1325         182 :             nWidth = DEFAULT_STRING_WIDTH;
    1326         182 :             if (const char *pszVal = CPLGetConfigOption(
    1327             :                     "OPENFILEGDB_DEFAULT_STRING_WIDTH", nullptr))
    1328             :             {
    1329           2 :                 const int nVal = atoi(pszVal);
    1330           2 :                 if (nVal >= 0)
    1331           2 :                     nWidth = nVal;
    1332             :             }
    1333             :             // Advertise a non-zero user-modified width back to the created
    1334             :             // OGRFieldDefn, only if it is less than the hard-coded default
    1335             :             // value (this will avoid potential issues with excessively large
    1336             :             // field width afterwards)
    1337         182 :             if (nWidth < DEFAULT_STRING_WIDTH)
    1338           2 :                 poField->SetWidth(nWidth);
    1339             :         }
    1340             :     }
    1341             : 
    1342         837 :     OGRField sDefault = FileGDBField::UNSET_FIELD;
    1343        1674 :     std::string osDefaultVal;
    1344         837 :     if (!GetDefault(poField, eType, sDefault, osDefaultVal,
    1345         837 :                     CPL_TO_BOOL(bApproxOK)))
    1346          12 :         return OGRERR_FAILURE;
    1347             : 
    1348         831 :     if (!poField->GetDomainName().empty() &&
    1349           6 :         (!m_osThisGUID.empty() ||
    1350         825 :          m_poDS->FindUUIDFromName(GetName(), m_osThisGUID)))
    1351             :     {
    1352           6 :         if (!m_poDS->LinkDomainToTable(poField->GetDomainName(), m_osThisGUID))
    1353             :         {
    1354           0 :             poField->SetDomainName(std::string());
    1355             :         }
    1356             :     }
    1357             : 
    1358             :     const bool bNullable =
    1359         825 :         CPL_TO_BOOL(poField->IsNullable()) && eType != FGFT_GLOBALID;
    1360         825 :     bool bRequired = (eType == FGFT_GLOBALID);
    1361         825 :     bool bEditable = (eType != FGFT_GLOBALID);
    1362             : 
    1363         825 :     if (poField->GetType() == OFTReal)
    1364             :     {
    1365         153 :         const char *pszDefault = poField->GetDefault();
    1366         153 :         if (pszDefault && EQUAL(pszDefault, "FILEGEODATABASE_SHAPE_AREA"))
    1367             :         {
    1368           4 :             m_iAreaField = m_poFeatureDefn->GetFieldCount();
    1369           4 :             bRequired = true;
    1370           4 :             bEditable = false;
    1371             :         }
    1372         149 :         else if (pszDefault &&
    1373          21 :                  EQUAL(pszDefault, "FILEGEODATABASE_SHAPE_LENGTH"))
    1374             :         {
    1375           7 :             m_iLengthField = m_poFeatureDefn->GetFieldCount();
    1376           7 :             bRequired = true;
    1377           7 :             bEditable = false;
    1378             :         }
    1379             :     }
    1380             : 
    1381         825 :     const char *pszAlias = poField->GetAlternativeNameRef();
    1382         825 :     if (!m_poLyrTable->CreateField(std::make_unique<FileGDBField>(
    1383         825 :             poField->GetNameRef(),
    1384        1650 :             pszAlias ? std::string(pszAlias) : std::string(), eType, bNullable,
    1385             :             bRequired, bEditable, nWidth, sDefault)))
    1386             :     {
    1387           9 :         return OGRERR_FAILURE;
    1388             :     }
    1389             : 
    1390         816 :     whileUnsealing(m_poFeatureDefn)->AddFieldDefn(poField);
    1391             : 
    1392         816 :     if (m_bRegisteredTable)
    1393             :     {
    1394             :         // If the table is already registered (that is updating an existing
    1395             :         // layer), patch the XML definition to add the new field
    1396          28 :         CPLXMLTreeCloser oTree(CPLParseXMLString(m_osDefinition.c_str()));
    1397          14 :         if (oTree)
    1398             :         {
    1399          14 :             CPLXMLNode *psGPFieldInfoExs = GetGPFieldInfoExsNode(oTree.get());
    1400          14 :             if (psGPFieldInfoExs)
    1401             :             {
    1402          14 :                 CPLAddXMLChild(psGPFieldInfoExs,
    1403             :                                CreateXMLFieldDefinition(
    1404             :                                    poField,
    1405          14 :                                    m_poLyrTable->GetField(
    1406          14 :                                        m_poLyrTable->GetFieldCount() - 1),
    1407          14 :                                    m_bArcGISPro32OrLater));
    1408             : 
    1409          14 :                 char *pszDefinition = CPLSerializeXMLTree(oTree.get());
    1410          14 :                 m_osDefinition = pszDefinition;
    1411          14 :                 CPLFree(pszDefinition);
    1412             : 
    1413          14 :                 m_poDS->UpdateXMLDefinition(m_osName.c_str(),
    1414             :                                             m_osDefinition.c_str());
    1415             :             }
    1416             :         }
    1417             :     }
    1418             :     else
    1419             :     {
    1420         802 :         RefreshXMLDefinitionInMemory();
    1421             :     }
    1422             : 
    1423         816 :     return OGRERR_NONE;
    1424             : }
    1425             : 
    1426             : /************************************************************************/
    1427             : /*                           AlterFieldDefn()                           */
    1428             : /************************************************************************/
    1429             : 
    1430          16 : OGRErr OGROpenFileGDBLayer::AlterFieldDefn(int iFieldToAlter,
    1431             :                                            OGRFieldDefn *poNewFieldDefn,
    1432             :                                            int nFlagsIn)
    1433             : {
    1434          16 :     if (!m_bEditable)
    1435           0 :         return OGRERR_FAILURE;
    1436             : 
    1437          16 :     if (!BuildLayerDefinition())
    1438           0 :         return OGRERR_FAILURE;
    1439             : 
    1440          16 :     if (m_poDS->IsInTransaction() &&
    1441           0 :         ((!m_bHasCreatedBackupForTransaction && !BeginEmulatedTransaction()) ||
    1442           0 :          !m_poDS->BackupSystemTablesForTransaction()))
    1443             :     {
    1444           0 :         return OGRERR_FAILURE;
    1445             :     }
    1446             : 
    1447          16 :     if (iFieldToAlter < 0 || iFieldToAlter >= m_poFeatureDefn->GetFieldCount())
    1448             :     {
    1449           2 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid field index");
    1450           2 :         return OGRERR_FAILURE;
    1451             :     }
    1452             : 
    1453          14 :     if (iFieldToAlter == m_iFIDAsRegularColumnIndex)
    1454             :     {
    1455           3 :         CPLError(CE_Failure, CPLE_NotSupported, "Cannot alter field %s",
    1456             :                  GetFIDColumn());
    1457           3 :         return OGRERR_FAILURE;
    1458             :     }
    1459             : 
    1460          22 :     const int nGDBIdx = m_poLyrTable->GetFieldIdx(
    1461          11 :         m_poFeatureDefn->GetFieldDefn(iFieldToAlter)->GetNameRef());
    1462          11 :     if (nGDBIdx < 0)
    1463           0 :         return OGRERR_FAILURE;
    1464             : 
    1465          11 :     OGRFieldDefn *poFieldDefn = m_poFeatureDefn->GetFieldDefn(iFieldToAlter);
    1466          22 :     auto oTemporaryUnsealer(poFieldDefn->GetTemporaryUnsealer());
    1467          22 :     OGRFieldDefn oField(poFieldDefn);
    1468          22 :     const std::string osOldFieldName(poFieldDefn->GetNameRef());
    1469             :     const std::string osOldDomainName(
    1470          22 :         std::string(poFieldDefn->GetDomainName()));
    1471          22 :     const bool bRenamedField = (nFlagsIn & ALTER_NAME_FLAG) != 0 &&
    1472          11 :                                poNewFieldDefn->GetNameRef() != osOldFieldName;
    1473             : 
    1474          11 :     if (nFlagsIn & ALTER_TYPE_FLAG)
    1475             :     {
    1476          17 :         if (poFieldDefn->GetType() != poNewFieldDefn->GetType() ||
    1477           8 :             poFieldDefn->GetSubType() != poNewFieldDefn->GetSubType())
    1478             :         {
    1479           2 :             CPLError(CE_Failure, CPLE_NotSupported,
    1480             :                      "Altering the field type is not supported");
    1481           2 :             return OGRERR_FAILURE;
    1482             :         }
    1483             :     }
    1484           9 :     if (nFlagsIn & ALTER_NAME_FLAG)
    1485             :     {
    1486           9 :         if (bRenamedField)
    1487             :         {
    1488           6 :             const std::string osFieldNameOri(poNewFieldDefn->GetNameRef());
    1489             :             const std::string osFieldNameLaundered =
    1490           6 :                 GetLaunderedFieldName(osFieldNameOri);
    1491           6 :             if (osFieldNameLaundered != osFieldNameOri)
    1492             :             {
    1493           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1494             :                          "Invalid field name: %s. "
    1495             :                          "A potential valid name would be: %s",
    1496             :                          osFieldNameOri.c_str(), osFieldNameLaundered.c_str());
    1497           1 :                 return OGRERR_FAILURE;
    1498             :             }
    1499             : 
    1500           5 :             oField.SetName(poNewFieldDefn->GetNameRef());
    1501             :         }
    1502             :     }
    1503           8 :     if (nFlagsIn & ALTER_WIDTH_PRECISION_FLAG)
    1504             :     {
    1505           6 :         if (oField.GetType() == OFTString)
    1506           4 :             oField.SetWidth(poNewFieldDefn->GetWidth());
    1507             :     }
    1508           8 :     if (nFlagsIn & ALTER_DEFAULT_FLAG)
    1509             :     {
    1510           6 :         oField.SetDefault(poNewFieldDefn->GetDefault());
    1511             :     }
    1512           8 :     if (nFlagsIn & ALTER_NULLABLE_FLAG)
    1513             :     {
    1514             :         // could be potentially done, but involves .gdbtable rewriting
    1515           6 :         if (poFieldDefn->IsNullable() != poNewFieldDefn->IsNullable())
    1516             :         {
    1517           1 :             CPLError(CE_Failure, CPLE_NotSupported,
    1518             :                      "Altering the nullable state of a field "
    1519             :                      "is not currently supported for OpenFileGDB");
    1520           1 :             return OGRERR_FAILURE;
    1521             :         }
    1522             :     }
    1523           7 :     if (nFlagsIn & ALTER_DOMAIN_FLAG)
    1524             :     {
    1525           5 :         oField.SetDomainName(poNewFieldDefn->GetDomainName());
    1526             :     }
    1527           7 :     if (nFlagsIn & ALTER_ALTERNATIVE_NAME_FLAG)
    1528             :     {
    1529           5 :         oField.SetAlternativeName(poNewFieldDefn->GetAlternativeNameRef());
    1530             :     }
    1531             : 
    1532           7 :     const auto eType = GetGDBFieldType(&oField, m_bArcGISPro32OrLater);
    1533             : 
    1534           7 :     int nWidth = 0;
    1535           7 :     if (eType == FGFT_GLOBALID || eType == FGFT_GUID)
    1536             :     {
    1537           0 :         nWidth = 38;
    1538             :     }
    1539           7 :     else if (oField.GetType() == OFTString)
    1540             :     {
    1541           3 :         nWidth = oField.GetWidth();
    1542           3 :         if (nWidth == 0)
    1543             :         {
    1544             :             // Can be useful to try to replicate FileGDB driver, but do
    1545             :             // not use its 65536 default value.
    1546           2 :             nWidth = atoi(CPLGetConfigOption("OPENFILEGDB_STRING_WIDTH", "0"));
    1547             :         }
    1548             :     }
    1549             : 
    1550           7 :     OGRField sDefault = FileGDBField::UNSET_FIELD;
    1551          14 :     std::string osDefaultVal;
    1552           7 :     if (!GetDefault(&oField, eType, sDefault, osDefaultVal,
    1553             :                     /*bApproxOK=*/false))
    1554           0 :         return OGRERR_FAILURE;
    1555             : 
    1556           7 :     const char *pszAlias = oField.GetAlternativeNameRef();
    1557           7 :     if (!m_poLyrTable->AlterField(
    1558             :             nGDBIdx, oField.GetNameRef(),
    1559          14 :             pszAlias ? std::string(pszAlias) : std::string(), eType,
    1560           7 :             CPL_TO_BOOL(oField.IsNullable()), nWidth, sDefault))
    1561             :     {
    1562           1 :         return OGRERR_FAILURE;
    1563             :     }
    1564             : 
    1565           6 :     poFieldDefn->SetSubType(OFSTNone);
    1566           6 :     poFieldDefn->SetName(oField.GetNameRef());
    1567           6 :     poFieldDefn->SetAlternativeName(oField.GetAlternativeNameRef());
    1568           6 :     poFieldDefn->SetType(oField.GetType());
    1569           6 :     poFieldDefn->SetSubType(oField.GetSubType());
    1570           6 :     poFieldDefn->SetWidth(oField.GetWidth());
    1571           6 :     poFieldDefn->SetDefault(oField.GetDefault());
    1572           6 :     poFieldDefn->SetNullable(oField.IsNullable());
    1573           6 :     poFieldDefn->SetDomainName(oField.GetDomainName());
    1574             : 
    1575           6 :     if (m_bRegisteredTable)
    1576             :     {
    1577             :         // If the table is already registered (that is updating an existing
    1578             :         // layer), patch the XML definition
    1579          10 :         CPLXMLTreeCloser oTree(CPLParseXMLString(m_osDefinition.c_str()));
    1580           5 :         if (oTree)
    1581             :         {
    1582           5 :             CPLXMLNode *psGPFieldInfoExs = GetGPFieldInfoExsNode(oTree.get());
    1583           5 :             if (psGPFieldInfoExs)
    1584             :             {
    1585           5 :                 CPLXMLNode *psLastChild = nullptr;
    1586          21 :                 for (CPLXMLNode *psIter = psGPFieldInfoExs->psChild; psIter;
    1587          16 :                      psIter = psIter->psNext)
    1588             :                 {
    1589          58 :                     if (psIter->eType == CXT_Element &&
    1590          37 :                         strcmp(psIter->pszValue, "GPFieldInfoEx") == 0 &&
    1591          16 :                         CPLGetXMLValue(psIter, "Name", "") == osOldFieldName)
    1592             :                     {
    1593           5 :                         CPLXMLNode *psNext = psIter->psNext;
    1594           5 :                         psIter->psNext = nullptr;
    1595           5 :                         CPLDestroyXMLNode(psIter);
    1596           5 :                         psIter = CreateXMLFieldDefinition(
    1597           5 :                             poFieldDefn, m_poLyrTable->GetField(nGDBIdx),
    1598           5 :                             m_bArcGISPro32OrLater);
    1599           5 :                         psIter->psNext = psNext;
    1600           5 :                         if (psLastChild == nullptr)
    1601           0 :                             psGPFieldInfoExs->psChild = psIter;
    1602             :                         else
    1603           5 :                             psLastChild->psNext = psIter;
    1604           5 :                         break;
    1605             :                     }
    1606          16 :                     psLastChild = psIter;
    1607             :                 }
    1608             : 
    1609           5 :                 if (bRenamedField && m_iAreaField == iFieldToAlter)
    1610             :                 {
    1611             :                     CPLXMLNode *psNode =
    1612           1 :                         CPLSearchXMLNode(oTree.get(), "=AreaFieldName");
    1613           1 :                     if (psNode)
    1614             :                     {
    1615           1 :                         CPLSetXMLValue(psNode, "", poFieldDefn->GetNameRef());
    1616           1 :                     }
    1617             :                 }
    1618           4 :                 else if (bRenamedField && m_iLengthField == iFieldToAlter)
    1619             :                 {
    1620             :                     CPLXMLNode *psNode =
    1621           1 :                         CPLSearchXMLNode(oTree.get(), "=LengthFieldName");
    1622           1 :                     if (psNode)
    1623             :                     {
    1624           1 :                         CPLSetXMLValue(psNode, "", poFieldDefn->GetNameRef());
    1625             :                     }
    1626             :                 }
    1627             : 
    1628           5 :                 char *pszDefinition = CPLSerializeXMLTree(oTree.get());
    1629           5 :                 m_osDefinition = pszDefinition;
    1630           5 :                 CPLFree(pszDefinition);
    1631             : 
    1632           5 :                 m_poDS->UpdateXMLDefinition(m_osName.c_str(),
    1633             :                                             m_osDefinition.c_str());
    1634             :             }
    1635             :         }
    1636             :     }
    1637             :     else
    1638             :     {
    1639           1 :         RefreshXMLDefinitionInMemory();
    1640             :     }
    1641             : 
    1642           7 :     if (osOldDomainName != oField.GetDomainName() &&
    1643           1 :         (!m_osThisGUID.empty() ||
    1644           7 :          m_poDS->FindUUIDFromName(GetName(), m_osThisGUID)))
    1645             :     {
    1646           1 :         if (osOldDomainName.empty())
    1647             :         {
    1648           1 :             if (!m_poDS->LinkDomainToTable(oField.GetDomainName(),
    1649           1 :                                            m_osThisGUID))
    1650             :             {
    1651           0 :                 poFieldDefn->SetDomainName(std::string());
    1652             :             }
    1653             :         }
    1654             :         else
    1655             :         {
    1656           0 :             bool bDomainStillUsed = false;
    1657           0 :             for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
    1658             :             {
    1659           0 :                 if (m_poFeatureDefn->GetFieldDefn(i)->GetDomainName() ==
    1660             :                     osOldDomainName)
    1661             :                 {
    1662           0 :                     bDomainStillUsed = true;
    1663           0 :                     break;
    1664             :                 }
    1665             :             }
    1666           0 :             if (!bDomainStillUsed)
    1667             :             {
    1668           0 :                 m_poDS->UnlinkDomainToTable(osOldDomainName, m_osThisGUID);
    1669             :             }
    1670             :         }
    1671             :     }
    1672             : 
    1673           6 :     return OGRERR_NONE;
    1674             : }
    1675             : 
    1676             : /************************************************************************/
    1677             : /*                         AlterGeomFieldDefn()                         */
    1678             : /************************************************************************/
    1679             : 
    1680           6 : OGRErr OGROpenFileGDBLayer::AlterGeomFieldDefn(
    1681             :     int iGeomFieldToAlter, const OGRGeomFieldDefn *poNewGeomFieldDefn,
    1682             :     int nFlagsIn)
    1683             : {
    1684           6 :     if (!m_bEditable)
    1685           0 :         return OGRERR_FAILURE;
    1686             : 
    1687           6 :     if (!BuildLayerDefinition())
    1688           0 :         return OGRERR_FAILURE;
    1689             : 
    1690           6 :     if (m_poDS->IsInTransaction() &&
    1691           0 :         ((!m_bHasCreatedBackupForTransaction && !BeginEmulatedTransaction()) ||
    1692           0 :          !m_poDS->BackupSystemTablesForTransaction()))
    1693             :     {
    1694           0 :         return OGRERR_FAILURE;
    1695             :     }
    1696             : 
    1697          12 :     if (iGeomFieldToAlter < 0 ||
    1698           6 :         iGeomFieldToAlter >= m_poFeatureDefn->GetGeomFieldCount())
    1699             :     {
    1700           0 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid field index");
    1701           0 :         return OGRERR_FAILURE;
    1702             :     }
    1703             : 
    1704          12 :     const int nGDBIdx = m_poLyrTable->GetFieldIdx(
    1705           6 :         m_poFeatureDefn->GetGeomFieldDefn(iGeomFieldToAlter)->GetNameRef());
    1706           6 :     if (nGDBIdx < 0)
    1707           0 :         return OGRERR_FAILURE;
    1708             : 
    1709             :     const auto poGeomFieldDefn =
    1710           6 :         m_poFeatureDefn->GetGeomFieldDefn(iGeomFieldToAlter);
    1711          12 :     auto oTemporaryUnsealer(poGeomFieldDefn->GetTemporaryUnsealer());
    1712          12 :     OGRGeomFieldDefn oField(poGeomFieldDefn);
    1713             : 
    1714           6 :     if ((nFlagsIn & ALTER_GEOM_FIELD_DEFN_TYPE_FLAG) != 0)
    1715             :     {
    1716           1 :         if (poGeomFieldDefn->GetType() != poNewGeomFieldDefn->GetType())
    1717             :         {
    1718           1 :             CPLError(CE_Failure, CPLE_NotSupported,
    1719             :                      "Altering the geometry field type is not supported for "
    1720             :                      "the FileGeodatabase format");
    1721           1 :             return OGRERR_FAILURE;
    1722             :         }
    1723             :     }
    1724             : 
    1725          10 :     const std::string osOldFieldName = poGeomFieldDefn->GetNameRef();
    1726             :     const bool bRenamedField =
    1727           6 :         (nFlagsIn & ALTER_GEOM_FIELD_DEFN_NAME_FLAG) != 0 &&
    1728           1 :         poNewGeomFieldDefn->GetNameRef() != osOldFieldName;
    1729           5 :     if ((nFlagsIn & ALTER_GEOM_FIELD_DEFN_NAME_FLAG) != 0)
    1730             :     {
    1731           1 :         if (bRenamedField)
    1732             :         {
    1733           1 :             const std::string osFieldNameOri(poNewGeomFieldDefn->GetNameRef());
    1734             :             const std::string osFieldNameLaundered =
    1735           1 :                 GetLaunderedFieldName(osFieldNameOri);
    1736           1 :             if (osFieldNameLaundered != osFieldNameOri)
    1737             :             {
    1738           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1739             :                          "Invalid field name: %s. "
    1740             :                          "A potential valid name would be: %s",
    1741             :                          osFieldNameOri.c_str(), osFieldNameLaundered.c_str());
    1742           0 :                 return OGRERR_FAILURE;
    1743             :             }
    1744             : 
    1745           1 :             oField.SetName(poNewGeomFieldDefn->GetNameRef());
    1746             :         }
    1747             :         // oField.SetAlternativeName(poNewGeomFieldDefn->GetAlternativeNameRef());
    1748             :     }
    1749             : 
    1750           5 :     if ((nFlagsIn & ALTER_GEOM_FIELD_DEFN_NULLABLE_FLAG) != 0)
    1751             :     {
    1752             :         // could be potentially done, but involves .gdbtable rewriting
    1753           1 :         if (poGeomFieldDefn->IsNullable() != poNewGeomFieldDefn->IsNullable())
    1754             :         {
    1755           1 :             CPLError(CE_Failure, CPLE_NotSupported,
    1756             :                      "Altering the nullable state of the geometry field "
    1757             :                      "is not currently supported for OpenFileGDB");
    1758           1 :             return OGRERR_FAILURE;
    1759             :         }
    1760             :     }
    1761             : 
    1762           4 :     if ((nFlagsIn & ALTER_GEOM_FIELD_DEFN_SRS_FLAG) != 0)
    1763             :     {
    1764           3 :         const auto poOldSRS = poGeomFieldDefn->GetSpatialRef();
    1765           3 :         const auto poNewSRS = poNewGeomFieldDefn->GetSpatialRef();
    1766             : 
    1767           3 :         const char *const apszOptions[] = {
    1768             :             "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr};
    1769           3 :         if ((poOldSRS == nullptr && poNewSRS != nullptr) ||
    1770           7 :             (poOldSRS != nullptr && poNewSRS == nullptr) ||
    1771           1 :             (poOldSRS != nullptr && poNewSRS != nullptr &&
    1772           1 :              !poOldSRS->IsSame(poNewSRS, apszOptions)))
    1773             :         {
    1774           3 :             if (!m_osFeatureDatasetGUID.empty())
    1775             :             {
    1776             :                 // Could potentially be done (would require changing the SRS
    1777             :                 // in all layers of the feature dataset)
    1778           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
    1779             :                          "Altering the SRS of the geometry field of a layer "
    1780             :                          "in a feature daaset is not currently supported "
    1781             :                          "for OpenFileGDB");
    1782           0 :                 return OGRERR_FAILURE;
    1783             :             }
    1784             : 
    1785             :             auto poNewSRSClone =
    1786           6 :                 OGRSpatialReferenceRefCountedPtr::makeClone(poNewSRS);
    1787           3 :             oField.SetSpatialRef(poNewSRSClone.get());
    1788             :         }
    1789             :     }
    1790             : 
    1791           8 :     std::string osWKT = "{B286C06B-0879-11D2-AACA-00C04FA33C20}";  // No SRS
    1792           4 :     if (oField.GetSpatialRef())
    1793             :     {
    1794           3 :         const char *const apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
    1795             :         char *pszWKT;
    1796           3 :         oField.GetSpatialRef()->exportToWkt(&pszWKT, apszOptions);
    1797           3 :         osWKT = pszWKT;
    1798           3 :         CPLFree(pszWKT);
    1799             :     }
    1800             : 
    1801           4 :     if (!m_poLyrTable->AlterGeomField(oField.GetNameRef(),
    1802           8 :                                       std::string(),  // Alias
    1803           4 :                                       CPL_TO_BOOL(oField.IsNullable()), osWKT))
    1804             :     {
    1805           0 :         return OGRERR_FAILURE;
    1806             :     }
    1807             : 
    1808           4 :     poGeomFieldDefn->SetName(oField.GetNameRef());
    1809           4 :     poGeomFieldDefn->SetSpatialRef(oField.GetSpatialRef());
    1810             : 
    1811           4 :     if (m_bRegisteredTable)
    1812             :     {
    1813             :         // If the table is already registered (that is updating an existing
    1814             :         // layer), patch the XML definition
    1815           8 :         CPLXMLTreeCloser oTree(CPLParseXMLString(m_osDefinition.c_str()));
    1816           4 :         if (oTree)
    1817             :         {
    1818           4 :             CPLXMLNode *psGPFieldInfoExs = GetGPFieldInfoExsNode(oTree.get());
    1819           4 :             if (psGPFieldInfoExs)
    1820             :             {
    1821           8 :                 for (CPLXMLNode *psIter = psGPFieldInfoExs->psChild; psIter;
    1822           4 :                      psIter = psIter->psNext)
    1823             :                 {
    1824          20 :                     if (psIter->eType == CXT_Element &&
    1825          12 :                         strcmp(psIter->pszValue, "GPFieldInfoEx") == 0 &&
    1826           4 :                         CPLGetXMLValue(psIter, "Name", "") == osOldFieldName)
    1827             :                     {
    1828           4 :                         CPLXMLNode *psNode = CPLGetXMLNode(psIter, "Name");
    1829           4 :                         if (psNode && psNode->psChild &&
    1830           4 :                             psNode->psChild->eType == CXT_Text)
    1831             :                         {
    1832           4 :                             CPLFree(psNode->psChild->pszValue);
    1833           8 :                             psNode->psChild->pszValue =
    1834           4 :                                 CPLStrdup(poGeomFieldDefn->GetNameRef());
    1835             :                         }
    1836           4 :                         break;
    1837             :                     }
    1838             :                 }
    1839             : 
    1840             :                 CPLXMLNode *psNode =
    1841           4 :                     CPLSearchXMLNode(oTree.get(), "=ShapeFieldName");
    1842           4 :                 if (psNode)
    1843             :                 {
    1844           4 :                     CPLSetXMLValue(psNode, "", poGeomFieldDefn->GetNameRef());
    1845             :                 }
    1846             : 
    1847             :                 CPLXMLNode *psFeatureClassInfo =
    1848           4 :                     CPLSearchXMLNode(oTree.get(), "=DEFeatureClassInfo");
    1849           4 :                 if (psFeatureClassInfo == nullptr)
    1850           4 :                     psFeatureClassInfo = CPLSearchXMLNode(
    1851             :                         oTree.get(), "=typens:DEFeatureClassInfo");
    1852           4 :                 if (psFeatureClassInfo)
    1853             :                 {
    1854           4 :                     psNode = CPLGetXMLNode(psFeatureClassInfo, "Extent");
    1855           4 :                     if (psNode)
    1856             :                     {
    1857           4 :                         if (CPLRemoveXMLChild(psFeatureClassInfo, psNode))
    1858           4 :                             CPLDestroyXMLNode(psNode);
    1859             :                     }
    1860             : 
    1861             :                     psNode =
    1862           4 :                         CPLGetXMLNode(psFeatureClassInfo, "SpatialReference");
    1863           4 :                     if (psNode)
    1864             :                     {
    1865           4 :                         if (CPLRemoveXMLChild(psFeatureClassInfo, psNode))
    1866           4 :                             CPLDestroyXMLNode(psNode);
    1867             :                     }
    1868             : 
    1869           4 :                     XMLSerializeGeomFieldBase(psFeatureClassInfo,
    1870           4 :                                               m_poLyrTable->GetGeomField(),
    1871           4 :                                               GetSpatialRef());
    1872             :                 }
    1873             : 
    1874           4 :                 char *pszDefinition = CPLSerializeXMLTree(oTree.get());
    1875           4 :                 m_osDefinition = pszDefinition;
    1876           4 :                 CPLFree(pszDefinition);
    1877             : 
    1878           4 :                 m_poDS->UpdateXMLDefinition(m_osName.c_str(),
    1879             :                                             m_osDefinition.c_str());
    1880             :             }
    1881             :         }
    1882             :     }
    1883             :     else
    1884             :     {
    1885           0 :         RefreshXMLDefinitionInMemory();
    1886             :     }
    1887             : 
    1888           4 :     return OGRERR_NONE;
    1889             : }
    1890             : 
    1891             : /************************************************************************/
    1892             : /*                            DeleteField()                             */
    1893             : /************************************************************************/
    1894             : 
    1895          18 : OGRErr OGROpenFileGDBLayer::DeleteField(int iFieldToDelete)
    1896             : {
    1897          18 :     if (!m_bEditable)
    1898           0 :         return OGRERR_FAILURE;
    1899             : 
    1900          18 :     if (!BuildLayerDefinition())
    1901           0 :         return OGRERR_FAILURE;
    1902             : 
    1903          19 :     if (m_poDS->IsInTransaction() &&
    1904           1 :         ((!m_bHasCreatedBackupForTransaction && !BeginEmulatedTransaction()) ||
    1905           1 :          !m_poDS->BackupSystemTablesForTransaction()))
    1906             :     {
    1907           0 :         return OGRERR_FAILURE;
    1908             :     }
    1909             : 
    1910          36 :     if (iFieldToDelete < 0 ||
    1911          18 :         iFieldToDelete >= m_poFeatureDefn->GetFieldCount())
    1912             :     {
    1913           0 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid field index");
    1914           0 :         return OGRERR_FAILURE;
    1915             :     }
    1916             : 
    1917          18 :     if (iFieldToDelete == m_iFIDAsRegularColumnIndex)
    1918             :     {
    1919           3 :         CPLError(CE_Failure, CPLE_NotSupported, "Cannot delete field %s",
    1920             :                  GetFIDColumn());
    1921           3 :         return OGRERR_FAILURE;
    1922             :     }
    1923             : 
    1924          15 :     const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(iFieldToDelete);
    1925          15 :     const int nGDBIdx = m_poLyrTable->GetFieldIdx(poFieldDefn->GetNameRef());
    1926          15 :     if (nGDBIdx < 0)
    1927           0 :         return OGRERR_FAILURE;
    1928          15 :     const bool bRet = m_poLyrTable->DeleteField(nGDBIdx);
    1929          15 :     m_iGeomFieldIdx = m_poLyrTable->GetGeomFieldIdx();
    1930             : 
    1931          15 :     if (!bRet)
    1932           0 :         return OGRERR_FAILURE;
    1933             : 
    1934          30 :     const std::string osDeletedFieldName = poFieldDefn->GetNameRef();
    1935             :     const std::string osOldDomainName =
    1936          15 :         std::string(poFieldDefn->GetDomainName());
    1937             : 
    1938          15 :     whileUnsealing(m_poFeatureDefn)->DeleteFieldDefn(iFieldToDelete);
    1939             : 
    1940          15 :     if (m_iFIDAsRegularColumnIndex > iFieldToDelete)
    1941           3 :         m_iFIDAsRegularColumnIndex--;
    1942             : 
    1943          15 :     if (iFieldToDelete < m_iAreaField)
    1944           0 :         m_iAreaField--;
    1945          15 :     if (iFieldToDelete < m_iLengthField)
    1946           0 :         m_iLengthField--;
    1947             : 
    1948          15 :     bool bEmptyAreaFieldName = false;
    1949          15 :     bool bEmptyLengthFieldName = false;
    1950          15 :     if (m_iAreaField == iFieldToDelete)
    1951             :     {
    1952           1 :         bEmptyAreaFieldName = true;
    1953           1 :         m_iAreaField = -1;
    1954             :     }
    1955          14 :     else if (m_iLengthField == iFieldToDelete)
    1956             :     {
    1957           1 :         bEmptyLengthFieldName = true;
    1958           1 :         m_iLengthField = -1;
    1959             :     }
    1960             : 
    1961          15 :     if (m_bRegisteredTable)
    1962             :     {
    1963             :         // If the table is already registered (that is updating an existing
    1964             :         // layer), patch the XML definition to add the new field
    1965          18 :         CPLXMLTreeCloser oTree(CPLParseXMLString(m_osDefinition.c_str()));
    1966           9 :         if (oTree)
    1967             :         {
    1968           9 :             CPLXMLNode *psGPFieldInfoExs = GetGPFieldInfoExsNode(oTree.get());
    1969           9 :             if (psGPFieldInfoExs)
    1970             :             {
    1971           9 :                 CPLXMLNode *psLastChild = nullptr;
    1972          34 :                 for (CPLXMLNode *psIter = psGPFieldInfoExs->psChild; psIter;
    1973          25 :                      psIter = psIter->psNext)
    1974             :                 {
    1975          93 :                     if (psIter->eType == CXT_Element &&
    1976          59 :                         strcmp(psIter->pszValue, "GPFieldInfoEx") == 0 &&
    1977          25 :                         CPLGetXMLValue(psIter, "Name", "") ==
    1978             :                             osDeletedFieldName)
    1979             :                     {
    1980           9 :                         if (psLastChild == nullptr)
    1981           0 :                             psGPFieldInfoExs->psChild = psIter->psNext;
    1982             :                         else
    1983           9 :                             psLastChild->psNext = psIter->psNext;
    1984           9 :                         psIter->psNext = nullptr;
    1985           9 :                         CPLDestroyXMLNode(psIter);
    1986           9 :                         break;
    1987             :                     }
    1988          25 :                     psLastChild = psIter;
    1989             :                 }
    1990             : 
    1991           9 :                 if (bEmptyAreaFieldName)
    1992             :                 {
    1993             :                     CPLXMLNode *psNode =
    1994           1 :                         CPLSearchXMLNode(oTree.get(), "=AreaFieldName");
    1995           1 :                     if (psNode && psNode->psChild)
    1996             :                     {
    1997           1 :                         CPLDestroyXMLNode(psNode->psChild);
    1998           1 :                         psNode->psChild = nullptr;
    1999             :                     }
    2000             :                 }
    2001           8 :                 else if (bEmptyLengthFieldName)
    2002             :                 {
    2003             :                     CPLXMLNode *psNode =
    2004           1 :                         CPLSearchXMLNode(oTree.get(), "=LengthFieldName");
    2005           1 :                     if (psNode && psNode->psChild)
    2006             :                     {
    2007           1 :                         CPLDestroyXMLNode(psNode->psChild);
    2008           1 :                         psNode->psChild = nullptr;
    2009             :                     }
    2010             :                 }
    2011             : 
    2012           9 :                 char *pszDefinition = CPLSerializeXMLTree(oTree.get());
    2013           9 :                 m_osDefinition = pszDefinition;
    2014           9 :                 CPLFree(pszDefinition);
    2015             : 
    2016           9 :                 m_poDS->UpdateXMLDefinition(m_osName.c_str(),
    2017             :                                             m_osDefinition.c_str());
    2018             :             }
    2019             :         }
    2020             :     }
    2021             :     else
    2022             :     {
    2023           6 :         RefreshXMLDefinitionInMemory();
    2024             :     }
    2025             : 
    2026          15 :     if (!osOldDomainName.empty())
    2027             :     {
    2028           3 :         bool bDomainStillUsed = false;
    2029           6 :         for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
    2030             :         {
    2031           3 :             if (m_poFeatureDefn->GetFieldDefn(i)->GetDomainName() ==
    2032             :                 osOldDomainName)
    2033             :             {
    2034           0 :                 bDomainStillUsed = true;
    2035           0 :                 break;
    2036             :             }
    2037             :         }
    2038           3 :         if (!bDomainStillUsed)
    2039             :         {
    2040           6 :             if (!m_osThisGUID.empty() ||
    2041           6 :                 m_poDS->FindUUIDFromName(GetName(), m_osThisGUID))
    2042             :             {
    2043           3 :                 m_poDS->UnlinkDomainToTable(osOldDomainName, m_osThisGUID);
    2044             :             }
    2045             :         }
    2046             :     }
    2047             : 
    2048          15 :     return OGRERR_NONE;
    2049             : }
    2050             : 
    2051             : /************************************************************************/
    2052             : /*                             GetLength()                              */
    2053             : /************************************************************************/
    2054             : 
    2055           9 : static double GetLength(const OGRCurvePolygon *poPoly)
    2056             : {
    2057           9 :     double dfLength = 0;
    2058          23 :     for (const auto *poRing : *poPoly)
    2059          14 :         dfLength += poRing->get_Length();
    2060           9 :     return dfLength;
    2061             : }
    2062             : 
    2063           4 : static double GetLength(const OGRMultiSurface *poMS)
    2064             : {
    2065           4 :     double dfLength = 0;
    2066          10 :     for (const auto *poPoly : *poMS)
    2067             :     {
    2068           6 :         auto poCurvePolygon = dynamic_cast<const OGRCurvePolygon *>(poPoly);
    2069           6 :         if (poCurvePolygon)
    2070           6 :             dfLength += GetLength(poCurvePolygon);
    2071             :     }
    2072           4 :     return dfLength;
    2073             : }
    2074             : 
    2075             : /************************************************************************/
    2076             : /*                       PrepareFileGDBFeature()                        */
    2077             : /************************************************************************/
    2078             : 
    2079        9552 : bool OGROpenFileGDBLayer::PrepareFileGDBFeature(OGRFeature *poFeature,
    2080             :                                                 std::vector<OGRField> &fields,
    2081             :                                                 const OGRGeometry *&poGeom,
    2082             :                                                 bool bUpdate)
    2083             : {
    2084             :     // Check geometry type
    2085        9552 :     poGeom = poFeature->GetGeometryRef();
    2086             :     const auto eFlattenType =
    2087        9552 :         poGeom ? wkbFlatten(poGeom->getGeometryType()) : wkbNone;
    2088        9552 :     if (poGeom)
    2089             :     {
    2090        4126 :         switch (m_poLyrTable->GetGeometryType())
    2091             :         {
    2092           0 :             case FGTGT_NONE:
    2093           0 :                 break;
    2094             : 
    2095        3810 :             case FGTGT_POINT:
    2096             :             {
    2097        3810 :                 if (eFlattenType != wkbPoint)
    2098             :                 {
    2099           3 :                     CPLError(
    2100             :                         CE_Failure, CPLE_NotSupported,
    2101             :                         "Can only insert a Point in a esriGeometryPoint layer");
    2102           3 :                     return false;
    2103             :                 }
    2104        3807 :                 break;
    2105             :             }
    2106             : 
    2107          31 :             case FGTGT_MULTIPOINT:
    2108             :             {
    2109          31 :                 if (eFlattenType != wkbMultiPoint)
    2110             :                 {
    2111           2 :                     CPLError(CE_Failure, CPLE_NotSupported,
    2112             :                              "Can only insert a MultiPoint in a "
    2113             :                              "esriGeometryMultiPoint layer");
    2114           2 :                     return false;
    2115             :                 }
    2116          29 :                 break;
    2117             :             }
    2118             : 
    2119          81 :             case FGTGT_LINE:
    2120             :             {
    2121          81 :                 if (eFlattenType != wkbLineString &&
    2122          13 :                     eFlattenType != wkbMultiLineString &&
    2123          11 :                     eFlattenType != wkbCircularString &&
    2124           7 :                     eFlattenType != wkbCompoundCurve &&
    2125             :                     eFlattenType != wkbMultiCurve)
    2126             :                 {
    2127           5 :                     CPLError(
    2128             :                         CE_Failure, CPLE_NotSupported,
    2129             :                         "Can only insert a "
    2130             :                         "LineString/MultiLineString/CircularString/"
    2131             :                         "CompoundCurve/MultiCurve in a esriGeometryLine layer");
    2132           5 :                     return false;
    2133             :                 }
    2134          76 :                 break;
    2135             :             }
    2136             : 
    2137         179 :             case FGTGT_POLYGON:
    2138             :             {
    2139         179 :                 if (eFlattenType != wkbPolygon &&
    2140          18 :                     eFlattenType != wkbMultiPolygon &&
    2141           8 :                     eFlattenType != wkbCurvePolygon &&
    2142             :                     eFlattenType != wkbMultiSurface)
    2143             :                 {
    2144           5 :                     CPLError(CE_Failure, CPLE_NotSupported,
    2145             :                              "Can only insert a "
    2146             :                              "Polygon/MultiPolygon/CurvePolygon/MultiSurface "
    2147             :                              "in a esriGeometryPolygon layer");
    2148           5 :                     return false;
    2149             :                 }
    2150         174 :                 break;
    2151             :             }
    2152             : 
    2153          25 :             case FGTGT_MULTIPATCH:
    2154             :             {
    2155          25 :                 if (eFlattenType != wkbTIN &&
    2156          15 :                     eFlattenType != wkbPolyhedralSurface &&
    2157             :                     eFlattenType != wkbGeometryCollection)
    2158             :                 {
    2159           2 :                     if (CPLTestBool(m_aosCreationOptions.FetchNameValueDef(
    2160           2 :                             "CREATE_MULTIPATCH", "FALSE")) &&
    2161             :                         eFlattenType == wkbMultiPolygon)
    2162             :                     {
    2163             :                         // ok
    2164             :                     }
    2165             :                     else
    2166             :                     {
    2167           2 :                         CPLError(
    2168             :                             CE_Failure, CPLE_NotSupported,
    2169             :                             "Can only insert a "
    2170             :                             "TIN/PolyhedralSurface/GeometryCollection in a "
    2171             :                             "esriGeometryMultiPatch layer");
    2172           2 :                         return false;
    2173             :                     }
    2174             :                 }
    2175          23 :                 break;
    2176             :             }
    2177             :         }
    2178             : 
    2179             :         // Treat empty geometries as NULL, like the FileGDB driver
    2180        4136 :         if (poGeom->IsEmpty() &&
    2181          27 :             !CPLTestBool(CPLGetConfigOption(
    2182             :                 "OGR_OPENFILEGDB_WRITE_EMPTY_GEOMETRY", "NO")))
    2183             :         {
    2184          15 :             poGeom = nullptr;
    2185             :         }
    2186             :     }
    2187             : 
    2188        9535 :     if (m_iAreaField >= 0)
    2189             :     {
    2190           8 :         const int i = m_iAreaField;
    2191           8 :         if (poGeom != nullptr)
    2192             :         {
    2193           7 :             if (eFlattenType == wkbPolygon || eFlattenType == wkbCurvePolygon)
    2194           3 :                 poFeature->SetField(i, poGeom->toCurvePolygon()->get_Area());
    2195           4 :             else if (eFlattenType == wkbMultiPolygon ||
    2196             :                      eFlattenType == wkbMultiSurface)
    2197           4 :                 poFeature->SetField(i, poGeom->toMultiSurface()->get_Area());
    2198             :             else
    2199           0 :                 poFeature->SetFieldNull(
    2200             :                     i);  // shouldn't happen in nominal situation
    2201             :         }
    2202             :         else
    2203             :         {
    2204           1 :             poFeature->SetFieldNull(i);
    2205             :         }
    2206             :     }
    2207             : 
    2208        9535 :     if (m_iLengthField >= 0)
    2209             :     {
    2210          14 :         const int i = m_iLengthField;
    2211          14 :         if (poGeom != nullptr)
    2212             :         {
    2213          12 :             if (OGR_GT_IsCurve(eFlattenType))
    2214           3 :                 poFeature->SetField(i, poGeom->toCurve()->get_Length());
    2215           9 :             else if (OGR_GT_IsSubClassOf(eFlattenType, wkbMultiCurve))
    2216           2 :                 poFeature->SetField(i, poGeom->toMultiCurve()->get_Length());
    2217           7 :             else if (eFlattenType == wkbPolygon ||
    2218             :                      eFlattenType == wkbCurvePolygon)
    2219           3 :                 poFeature->SetField(i, GetLength(poGeom->toCurvePolygon()));
    2220           4 :             else if (eFlattenType == wkbMultiPolygon ||
    2221             :                      eFlattenType == wkbMultiSurface)
    2222           4 :                 poFeature->SetField(i, GetLength(poGeom->toMultiSurface()));
    2223             :             else
    2224           0 :                 poFeature->SetFieldNull(
    2225             :                     i);  // shouldn't happen in nominal situation
    2226             :         }
    2227             :         else
    2228             :         {
    2229           2 :             poFeature->SetFieldNull(i);
    2230             :         }
    2231             :     }
    2232             : 
    2233        9535 :     fields.resize(m_poLyrTable->GetFieldCount(), FileGDBField::UNSET_FIELD);
    2234        9535 :     m_aosTempStrings.clear();
    2235       17773 :     for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
    2236             :     {
    2237        8238 :         if (i == m_iFIDAsRegularColumnIndex)
    2238           9 :             continue;
    2239        8229 :         const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(i);
    2240             :         const int idxFileGDB =
    2241        8229 :             m_poLyrTable->GetFieldIdx(poFieldDefn->GetNameRef());
    2242        8229 :         if (idxFileGDB < 0)
    2243           0 :             continue;
    2244        8229 :         if (!poFeature->IsFieldSetAndNotNull(i))
    2245             :         {
    2246         148 :             if (m_poLyrTable->GetField(idxFileGDB)->GetType() == FGFT_GLOBALID)
    2247             :             {
    2248           6 :                 m_aosTempStrings.emplace_back(OFGDBGenerateUUID());
    2249           6 :                 fields[idxFileGDB].String = &m_aosTempStrings.back()[0];
    2250             :             }
    2251         148 :             continue;
    2252             :         }
    2253        8081 :         memset(&fields[idxFileGDB], 0, sizeof(OGRField));
    2254        8081 :         switch (m_poLyrTable->GetField(idxFileGDB)->GetType())
    2255             :         {
    2256           0 :             case FGFT_UNDEFINED:
    2257           0 :                 CPLAssert(false);
    2258             :                 break;
    2259         343 :             case FGFT_INT16:
    2260         686 :                 fields[idxFileGDB].Integer =
    2261         343 :                     poFeature->GetRawFieldRef(i)->Integer;
    2262         343 :                 break;
    2263         492 :             case FGFT_INT32:
    2264         984 :                 fields[idxFileGDB].Integer =
    2265         492 :                     poFeature->GetRawFieldRef(i)->Integer;
    2266         492 :                 break;
    2267         343 :             case FGFT_FLOAT32:
    2268         343 :                 fields[idxFileGDB].Real = poFeature->GetRawFieldRef(i)->Real;
    2269         343 :                 break;
    2270         351 :             case FGFT_FLOAT64:
    2271             :             {
    2272         351 :                 if (poFieldDefn->GetType() == OFTReal)
    2273             :                 {
    2274         309 :                     fields[idxFileGDB].Real =
    2275         309 :                         poFeature->GetRawFieldRef(i)->Real;
    2276             :                 }
    2277             :                 else
    2278             :                 {
    2279          42 :                     fields[idxFileGDB].Real = poFeature->GetFieldAsDouble(i);
    2280             :                 }
    2281         351 :                 break;
    2282             :             }
    2283        5933 :             case FGFT_STRING:
    2284             :             case FGFT_GUID:
    2285             :             case FGFT_XML:
    2286             :             {
    2287        5933 :                 if (poFieldDefn->GetType() == OFTString)
    2288             :                 {
    2289        5933 :                     fields[idxFileGDB].String =
    2290        5933 :                         poFeature->GetRawFieldRef(i)->String;
    2291             :                 }
    2292             :                 else
    2293             :                 {
    2294             :                     m_aosTempStrings.emplace_back(
    2295           0 :                         poFeature->GetFieldAsString(i));
    2296           0 :                     fields[idxFileGDB].String = &m_aosTempStrings.back()[0];
    2297             :                 }
    2298        5933 :                 break;
    2299             :             }
    2300         237 :             case FGFT_DATETIME:
    2301             :             case FGFT_DATE:
    2302             :             {
    2303         237 :                 fields[idxFileGDB].Date = poFeature->GetRawFieldRef(i)->Date;
    2304         237 :                 if (m_bTimeInUTC && fields[idxFileGDB].Date.TZFlag <= 1)
    2305             :                 {
    2306          94 :                     if (!m_bRegisteredTable &&
    2307         188 :                         m_poLyrTable->GetTotalRecordCount() == 0 &&
    2308          36 :                         m_aosCreationOptions.FetchNameValue("TIME_IN_UTC") ==
    2309             :                             nullptr)
    2310             :                     {
    2311             :                         // If the user didn't explicitly set TIME_IN_UTC, and
    2312             :                         // this is the first feature written, automatically
    2313             :                         // adjust m_bTimeInUTC from the first value
    2314          36 :                         m_bTimeInUTC = false;
    2315             :                     }
    2316          58 :                     else if (!m_bWarnedDateNotConvertibleUTC)
    2317             :                     {
    2318          14 :                         m_bWarnedDateNotConvertibleUTC = true;
    2319          14 :                         CPLError(
    2320             :                             CE_Warning, CPLE_AppDefined,
    2321             :                             "Attempt at writing a datetime with a unknown time "
    2322             :                             "zone "
    2323             :                             "or local time in a layer that expects dates "
    2324             :                             "to be convertible to UTC. It will be written as "
    2325             :                             "if it was expressed in UTC.");
    2326             :                     }
    2327             :                 }
    2328         237 :                 break;
    2329             :             }
    2330           0 :             case FGFT_OBJECTID:
    2331           0 :                 CPLAssert(false);
    2332             :                 break;  // shouldn't happen
    2333           0 :             case FGFT_GEOMETRY:
    2334           0 :                 CPLAssert(false);
    2335             :                 break;  // shouldn't happen
    2336           0 :             case FGFT_RASTER:
    2337           0 :                 CPLAssert(false);
    2338             :                 break;  // shouldn't happen
    2339         342 :             case FGFT_BINARY:
    2340         342 :                 fields[idxFileGDB].Binary =
    2341         342 :                     poFeature->GetRawFieldRef(i)->Binary;
    2342         342 :                 break;
    2343           0 :             case FGFT_GLOBALID:
    2344             :             {
    2345           0 :                 if (bUpdate)
    2346             :                 {
    2347             :                     m_aosTempStrings.emplace_back(
    2348           0 :                         poFeature->GetFieldAsString(i));
    2349           0 :                     fields[idxFileGDB].String = &m_aosTempStrings.back()[0];
    2350             :                 }
    2351           0 :                 else if (poFeature->GetRawFieldRef(i)->String[0] != '\0')
    2352             :                 {
    2353           0 :                     if (CPLTestBool(CPLGetConfigOption(
    2354             :                             "OPENFILEGDB_REGENERATE_GLOBALID", "YES")))
    2355             :                     {
    2356           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    2357             :                                  "Value found in a GlobalID field. It will be "
    2358             :                                  "replaced by a "
    2359             :                                  "newly generated UUID.");
    2360           0 :                         m_aosTempStrings.emplace_back(OFGDBGenerateUUID());
    2361           0 :                         fields[idxFileGDB].String = &m_aosTempStrings.back()[0];
    2362             :                     }
    2363             :                     else
    2364             :                     {
    2365             :                         m_aosTempStrings.emplace_back(
    2366           0 :                             poFeature->GetFieldAsString(i));
    2367           0 :                         fields[idxFileGDB].String = &m_aosTempStrings.back()[0];
    2368             :                     }
    2369             :                 }
    2370             :                 else
    2371             :                 {
    2372           0 :                     m_aosTempStrings.emplace_back(OFGDBGenerateUUID());
    2373           0 :                     fields[idxFileGDB].String = &m_aosTempStrings.back()[0];
    2374             :                 }
    2375           0 :                 break;
    2376             :             }
    2377          22 :             case FGFT_INT64:
    2378             :             {
    2379          44 :                 fields[idxFileGDB].Integer64 =
    2380          22 :                     poFeature->GetRawFieldRef(i)->Integer64;
    2381          22 :                 break;
    2382             :             }
    2383          18 :             case FGFT_TIME:
    2384             :             case FGFT_DATETIME_WITH_OFFSET:
    2385             :             {
    2386          18 :                 fields[idxFileGDB].Date = poFeature->GetRawFieldRef(i)->Date;
    2387          18 :                 break;
    2388             :             }
    2389             :         }
    2390             :     }
    2391             : 
    2392        9535 :     return true;
    2393             : }
    2394             : 
    2395             : /************************************************************************/
    2396             : /*                  CheckFIDAndFIDColumnConsistency()                   */
    2397             : /************************************************************************/
    2398             : 
    2399           9 : static bool CheckFIDAndFIDColumnConsistency(const OGRFeature *poFeature,
    2400             :                                             int iFIDAsRegularColumnIndex)
    2401             : {
    2402           9 :     bool ok = false;
    2403           9 :     if (!poFeature->IsFieldSetAndNotNull(iFIDAsRegularColumnIndex))
    2404             :     {
    2405             :         // nothing to do
    2406             :     }
    2407           9 :     else if (poFeature->GetDefnRef()
    2408           9 :                  ->GetFieldDefn(iFIDAsRegularColumnIndex)
    2409           9 :                  ->GetType() == OFTReal)
    2410             :     {
    2411             :         const double dfFID =
    2412           3 :             poFeature->GetFieldAsDouble(iFIDAsRegularColumnIndex);
    2413           3 :         if (GDALIsValueInRange<int64_t>(dfFID))
    2414             :         {
    2415           3 :             const auto nFID = static_cast<GIntBig>(dfFID);
    2416           3 :             if (nFID == poFeature->GetFID())
    2417             :             {
    2418           2 :                 ok = true;
    2419             :             }
    2420             :         }
    2421             :     }
    2422          12 :     else if (poFeature->GetFieldAsInteger64(iFIDAsRegularColumnIndex) ==
    2423           6 :              poFeature->GetFID())
    2424             :     {
    2425           4 :         ok = true;
    2426             :     }
    2427           9 :     if (!ok)
    2428             :     {
    2429           3 :         CPLError(CE_Failure, CPLE_AppDefined,
    2430             :                  "Inconsistent values of FID and field of same name");
    2431             :     }
    2432           9 :     return ok;
    2433             : }
    2434             : 
    2435             : /************************************************************************/
    2436             : /*                           ICreateFeature()                           */
    2437             : /************************************************************************/
    2438             : 
    2439        9546 : OGRErr OGROpenFileGDBLayer::ICreateFeature(OGRFeature *poFeature)
    2440             : {
    2441        9546 :     if (!m_bEditable)
    2442           0 :         return OGRERR_FAILURE;
    2443             : 
    2444        9546 :     if (!BuildLayerDefinition())
    2445           0 :         return OGRERR_FAILURE;
    2446             : 
    2447        9548 :     if (m_poDS->IsInTransaction() && !m_bHasCreatedBackupForTransaction &&
    2448           2 :         !BeginEmulatedTransaction())
    2449             :     {
    2450           0 :         return OGRERR_FAILURE;
    2451             :     }
    2452             : 
    2453             :     /* In case the FID column has also been created as a regular field */
    2454        9546 :     if (m_iFIDAsRegularColumnIndex >= 0)
    2455             :     {
    2456           9 :         if (poFeature->GetFID() == OGRNullFID)
    2457             :         {
    2458           3 :             if (poFeature->IsFieldSetAndNotNull(m_iFIDAsRegularColumnIndex))
    2459             :             {
    2460           6 :                 if (m_poFeatureDefn->GetFieldDefn(m_iFIDAsRegularColumnIndex)
    2461           3 :                         ->GetType() == OFTReal)
    2462             :                 {
    2463           1 :                     bool ok = false;
    2464             :                     const double dfFID =
    2465           1 :                         poFeature->GetFieldAsDouble(m_iFIDAsRegularColumnIndex);
    2466           2 :                     if (dfFID >= static_cast<double>(
    2467           2 :                                      std::numeric_limits<int64_t>::min()) &&
    2468           1 :                         dfFID <= static_cast<double>(
    2469           1 :                                      std::numeric_limits<int64_t>::max()))
    2470             :                     {
    2471           1 :                         const auto nFID = static_cast<GIntBig>(dfFID);
    2472           1 :                         if (static_cast<double>(nFID) == dfFID)
    2473             :                         {
    2474           1 :                             poFeature->SetFID(nFID);
    2475           1 :                             ok = true;
    2476             :                         }
    2477             :                     }
    2478           1 :                     if (!ok)
    2479             :                     {
    2480           0 :                         CPLError(
    2481             :                             CE_Failure, CPLE_AppDefined,
    2482             :                             "Value of FID %g cannot be parsed to an Integer64",
    2483             :                             dfFID);
    2484           0 :                         return OGRERR_FAILURE;
    2485             :                     }
    2486             :                 }
    2487             :                 else
    2488             :                 {
    2489           2 :                     poFeature->SetFID(poFeature->GetFieldAsInteger64(
    2490           2 :                         m_iFIDAsRegularColumnIndex));
    2491             :                 }
    2492             :             }
    2493             :         }
    2494           6 :         else if (!CheckFIDAndFIDColumnConsistency(poFeature,
    2495             :                                                   m_iFIDAsRegularColumnIndex))
    2496             :         {
    2497           3 :             return OGRERR_FAILURE;
    2498             :         }
    2499             :     }
    2500             : 
    2501        9543 :     const auto nFID64Bit = poFeature->GetFID();
    2502        9543 :     if (nFID64Bit < -1 || nFID64Bit == 0 || nFID64Bit > INT_MAX)
    2503             :     {
    2504           6 :         CPLError(CE_Failure, CPLE_NotSupported,
    2505             :                  "Only 32 bit positive integers FID supported by FileGDB");
    2506           6 :         return OGRERR_FAILURE;
    2507             :     }
    2508             : 
    2509        9537 :     int nFID32Bit = (nFID64Bit > 0) ? static_cast<int>(nFID64Bit) : 0;
    2510             : 
    2511        9537 :     poFeature->FillUnsetWithDefault(FALSE, nullptr);
    2512             : 
    2513        9537 :     const OGRGeometry *poGeom = nullptr;
    2514       19074 :     std::vector<OGRField> fields;
    2515        9537 :     if (!PrepareFileGDBFeature(poFeature, fields, poGeom, /*bUpdate=*/false))
    2516          17 :         return OGRERR_FAILURE;
    2517             : 
    2518        9520 :     m_eSpatialIndexState = SPI_INVALID;
    2519        9520 :     m_nFilteredFeatureCount = -1;
    2520             : 
    2521        9520 :     if (!m_poLyrTable->CreateFeature(fields, poGeom, &nFID32Bit))
    2522           8 :         return OGRERR_FAILURE;
    2523             : 
    2524        9512 :     poFeature->SetFID(nFID32Bit);
    2525        9512 :     return OGRERR_NONE;
    2526             : }
    2527             : 
    2528             : /************************************************************************/
    2529             : /*                            ISetFeature()                             */
    2530             : /************************************************************************/
    2531             : 
    2532         105 : OGRErr OGROpenFileGDBLayer::ISetFeature(OGRFeature *poFeature)
    2533             : {
    2534         105 :     if (!m_bEditable)
    2535          86 :         return OGRERR_FAILURE;
    2536             : 
    2537          19 :     if (!BuildLayerDefinition())
    2538           0 :         return OGRERR_FAILURE;
    2539             : 
    2540          19 :     if (m_poDS->IsInTransaction() && !m_bHasCreatedBackupForTransaction &&
    2541           0 :         !BeginEmulatedTransaction())
    2542             :     {
    2543           0 :         return OGRERR_FAILURE;
    2544             :     }
    2545             : 
    2546             :     /* In case the FID column has also been created as a regular field */
    2547          22 :     if (m_iFIDAsRegularColumnIndex >= 0 &&
    2548           3 :         !CheckFIDAndFIDColumnConsistency(poFeature, m_iFIDAsRegularColumnIndex))
    2549             :     {
    2550           0 :         return OGRERR_FAILURE;
    2551             :     }
    2552             : 
    2553          19 :     const GIntBig nFID = poFeature->GetFID();
    2554          19 :     if (nFID <= 0 || !CPL_INT64_FITS_ON_INT32(nFID))
    2555           2 :         return OGRERR_NON_EXISTING_FEATURE;
    2556             : 
    2557          17 :     const int nFID32Bit = static_cast<int>(nFID);
    2558          17 :     if (nFID32Bit > m_poLyrTable->GetTotalRecordCount())
    2559           1 :         return OGRERR_NON_EXISTING_FEATURE;
    2560          16 :     if (!m_poLyrTable->SelectRow(nFID32Bit - 1))
    2561           1 :         return OGRERR_NON_EXISTING_FEATURE;
    2562             : 
    2563          15 :     const OGRGeometry *poGeom = nullptr;
    2564          30 :     std::vector<OGRField> fields;
    2565          15 :     if (!PrepareFileGDBFeature(poFeature, fields, poGeom, /*bUpdate=*/true))
    2566           0 :         return OGRERR_FAILURE;
    2567             : 
    2568          15 :     m_eSpatialIndexState = SPI_INVALID;
    2569          15 :     m_nFilteredFeatureCount = -1;
    2570             : 
    2571          15 :     if (!m_poLyrTable->UpdateFeature(nFID32Bit, fields, poGeom))
    2572           0 :         return OGRERR_FAILURE;
    2573             : 
    2574          15 :     return OGRERR_NONE;
    2575             : }
    2576             : 
    2577             : /************************************************************************/
    2578             : /*                           DeleteFeature()                            */
    2579             : /************************************************************************/
    2580             : 
    2581        2796 : OGRErr OGROpenFileGDBLayer::DeleteFeature(GIntBig nFID)
    2582             : 
    2583             : {
    2584        2796 :     if (!m_bEditable)
    2585         178 :         return OGRERR_FAILURE;
    2586             : 
    2587        2618 :     if (!BuildLayerDefinition())
    2588           0 :         return OGRERR_FAILURE;
    2589             : 
    2590        2618 :     if (m_poDS->IsInTransaction() && !m_bHasCreatedBackupForTransaction &&
    2591           0 :         !BeginEmulatedTransaction())
    2592             :     {
    2593           0 :         return OGRERR_FAILURE;
    2594             :     }
    2595             : 
    2596        2618 :     if (nFID <= 0 || !CPL_INT64_FITS_ON_INT32(nFID))
    2597           3 :         return OGRERR_NON_EXISTING_FEATURE;
    2598             : 
    2599        2615 :     const int nFID32Bit = static_cast<int>(nFID);
    2600        2615 :     if (nFID32Bit > m_poLyrTable->GetTotalRecordCount())
    2601           2 :         return OGRERR_NON_EXISTING_FEATURE;
    2602        2613 :     if (!m_poLyrTable->SelectRow(nFID32Bit - 1))
    2603           1 :         return OGRERR_NON_EXISTING_FEATURE;
    2604             : 
    2605        2612 :     m_eSpatialIndexState = SPI_INVALID;
    2606        2612 :     m_nFilteredFeatureCount = -1;
    2607             : 
    2608        2612 :     return m_poLyrTable->DeleteFeature(nFID32Bit) ? OGRERR_NONE
    2609        2612 :                                                   : OGRERR_FAILURE;
    2610             : }
    2611             : 
    2612             : /************************************************************************/
    2613             : /*                    RefreshXMLDefinitionInMemory()                    */
    2614             : /************************************************************************/
    2615             : 
    2616        1139 : void OGROpenFileGDBLayer::RefreshXMLDefinitionInMemory()
    2617             : {
    2618        1139 :     CPLXMLTreeCloser oTree(CPLCreateXMLNode(nullptr, CXT_Element, "?xml"));
    2619        1139 :     CPLAddXMLAttributeAndValue(oTree.get(), "version", "1.0");
    2620        1139 :     CPLAddXMLAttributeAndValue(oTree.get(), "encoding", "UTF-8");
    2621             : 
    2622             :     CPLXMLNode *psRoot =
    2623        1139 :         CPLCreateXMLNode(nullptr, CXT_Element,
    2624        1139 :                          m_eGeomType == wkbNone ? "typens:DETableInfo"
    2625             :                                                 : "typens:DEFeatureClassInfo");
    2626        1139 :     CPLAddXMLSibling(oTree.get(), psRoot);
    2627             : 
    2628        1139 :     CPLAddXMLAttributeAndValue(psRoot, "xmlns:typens",
    2629        1139 :                                m_bArcGISPro32OrLater
    2630             :                                    ? "http://www.esri.com/schemas/ArcGIS/10.8"
    2631             :                                    : "http://www.esri.com/schemas/ArcGIS/10.3");
    2632        1139 :     CPLAddXMLAttributeAndValue(psRoot, "xmlns:xsi",
    2633             :                                "http://www.w3.org/2001/XMLSchema-instance");
    2634        1139 :     CPLAddXMLAttributeAndValue(psRoot, "xmlns:xs",
    2635             :                                "http://www.w3.org/2001/XMLSchema");
    2636        1139 :     CPLAddXMLAttributeAndValue(psRoot, "xsi:type",
    2637        1139 :                                m_eGeomType == wkbNone
    2638             :                                    ? "typens:DETableInfo"
    2639             :                                    : "typens:DEFeatureClassInfo");
    2640        1139 :     CPLCreateXMLElementAndValue(psRoot, "CatalogPath", m_osPath.c_str());
    2641        1139 :     CPLCreateXMLElementAndValue(psRoot, "Name", m_osName.c_str());
    2642        1139 :     CPLCreateXMLElementAndValue(psRoot, "ChildrenExpanded", "false");
    2643        1139 :     CPLCreateXMLElementAndValue(psRoot, "DatasetType",
    2644        1139 :                                 m_eGeomType == wkbNone ? "esriDTTable"
    2645             :                                                        : "esriDTFeatureClass");
    2646             : 
    2647             :     {
    2648        1139 :         FileGDBTable oTable;
    2649        1139 :         if (!oTable.Open(m_poDS->m_osGDBItemsFilename.c_str(), false))
    2650           0 :             return;
    2651        1139 :         CPLCreateXMLElementAndValue(
    2652             :             psRoot, "DSID",
    2653        1139 :             CPLSPrintf("%" PRId64, 1 + oTable.GetTotalRecordCount()));
    2654             :     }
    2655             : 
    2656        1139 :     CPLCreateXMLElementAndValue(psRoot, "Versioned", "false");
    2657        1139 :     CPLCreateXMLElementAndValue(psRoot, "CanVersion", "false");
    2658        1139 :     if (!m_osConfigurationKeyword.empty())
    2659             :     {
    2660           2 :         CPLCreateXMLElementAndValue(psRoot, "ConfigurationKeyword",
    2661             :                                     m_osConfigurationKeyword.c_str());
    2662             :     }
    2663        1139 :     if (m_bArcGISPro32OrLater)
    2664             :     {
    2665          20 :         CPLCreateXMLElementAndValue(psRoot, "RequiredGeodatabaseClientVersion",
    2666             :                                     "13.2");
    2667             :     }
    2668        1139 :     CPLCreateXMLElementAndValue(psRoot, "HasOID", "true");
    2669        1139 :     CPLCreateXMLElementAndValue(psRoot, "OIDFieldName", GetFIDColumn());
    2670             :     auto GPFieldInfoExs =
    2671        1139 :         CPLCreateXMLNode(psRoot, CXT_Element, "GPFieldInfoExs");
    2672        1139 :     CPLAddXMLAttributeAndValue(GPFieldInfoExs, "xsi:type",
    2673             :                                "typens:ArrayOfGPFieldInfoEx");
    2674             : 
    2675        7280 :     for (int i = 0; i < m_poLyrTable->GetFieldCount(); ++i)
    2676             :     {
    2677        6141 :         const auto *poGDBFieldDefn = m_poLyrTable->GetField(i);
    2678        6141 :         if (poGDBFieldDefn->GetType() == FGFT_OBJECTID)
    2679             :         {
    2680             :             auto GPFieldInfoEx =
    2681        1139 :                 CPLCreateXMLNode(GPFieldInfoExs, CXT_Element, "GPFieldInfoEx");
    2682        1139 :             CPLAddXMLAttributeAndValue(GPFieldInfoEx, "xsi:type",
    2683             :                                        "typens:GPFieldInfoEx");
    2684        1139 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Name",
    2685        1139 :                                         poGDBFieldDefn->GetName().c_str());
    2686        1139 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "FieldType",
    2687             :                                         "esriFieldTypeOID");
    2688        1139 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "IsNullable", "false");
    2689        1139 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Length", "4");
    2690        1139 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Precision", "0");
    2691        1139 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Scale", "0");
    2692        1139 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Required", "true");
    2693             :         }
    2694        5002 :         else if (poGDBFieldDefn->GetType() == FGFT_GEOMETRY)
    2695             :         {
    2696             :             auto GPFieldInfoEx =
    2697         923 :                 CPLCreateXMLNode(GPFieldInfoExs, CXT_Element, "GPFieldInfoEx");
    2698         923 :             CPLAddXMLAttributeAndValue(GPFieldInfoEx, "xsi:type",
    2699             :                                        "typens:GPFieldInfoEx");
    2700         923 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Name",
    2701         923 :                                         poGDBFieldDefn->GetName().c_str());
    2702         923 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "FieldType",
    2703             :                                         "esriFieldTypeGeometry");
    2704         923 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "IsNullable",
    2705         923 :                                         poGDBFieldDefn->IsNullable() ? "true"
    2706             :                                                                      : "false");
    2707         923 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Length", "0");
    2708         923 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Precision", "0");
    2709         923 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Scale", "0");
    2710         923 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Required", "true");
    2711             :         }
    2712             :         else
    2713             :         {
    2714        4079 :             const int nOGRIdx = m_poFeatureDefn->GetFieldIndex(
    2715        4079 :                 poGDBFieldDefn->GetName().c_str());
    2716        4079 :             if (nOGRIdx >= 0)
    2717             :             {
    2718        4079 :                 const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(nOGRIdx);
    2719        4079 :                 CPLAddXMLChild(GPFieldInfoExs, CreateXMLFieldDefinition(
    2720             :                                                    poFieldDefn, poGDBFieldDefn,
    2721        4079 :                                                    m_bArcGISPro32OrLater));
    2722             :             }
    2723             :         }
    2724             :     }
    2725             : 
    2726        1139 :     CPLCreateXMLElementAndValue(psRoot, "CLSID",
    2727        1139 :                                 m_eGeomType == wkbNone
    2728             :                                     ? "{7A566981-C114-11D2-8A28-006097AFF44E}"
    2729             :                                     : "{52353152-891A-11D0-BEC6-00805F7C4268}");
    2730        1139 :     CPLCreateXMLElementAndValue(psRoot, "EXTCLSID", "");
    2731             : 
    2732             :     const char *pszLayerAlias =
    2733        1139 :         m_aosCreationOptions.FetchNameValue("LAYER_ALIAS");
    2734        1139 :     if (pszLayerAlias != nullptr)
    2735             :     {
    2736           1 :         CPLCreateXMLElementAndValue(psRoot, "AliasName", pszLayerAlias);
    2737             :     }
    2738             : 
    2739        1139 :     CPLCreateXMLElementAndValue(psRoot, "IsTimeInUTC",
    2740        1139 :                                 m_bTimeInUTC ? "true" : " false");
    2741             : 
    2742        1139 :     if (m_eGeomType != wkbNone)
    2743             :     {
    2744         923 :         const auto poGeomFieldDefn = m_poLyrTable->GetGeomField();
    2745         923 :         CPLCreateXMLElementAndValue(psRoot, "FeatureType", "esriFTSimple");
    2746             : 
    2747         923 :         const char *pszShapeType = "";
    2748         923 :         switch (m_poLyrTable->GetGeometryType())
    2749             :         {
    2750           0 :             case FGTGT_NONE:
    2751           0 :                 break;
    2752         315 :             case FGTGT_POINT:
    2753         315 :                 pszShapeType = "esriGeometryPoint";
    2754         315 :                 break;
    2755          79 :             case FGTGT_MULTIPOINT:
    2756          79 :                 pszShapeType = "esriGeometryMultipoint";
    2757          79 :                 break;
    2758         182 :             case FGTGT_LINE:
    2759         182 :                 pszShapeType = "esriGeometryPolyline";
    2760         182 :                 break;
    2761         281 :             case FGTGT_POLYGON:
    2762         281 :                 pszShapeType = "esriGeometryPolygon";
    2763         281 :                 break;
    2764          66 :             case FGTGT_MULTIPATCH:
    2765          66 :                 pszShapeType = "esriGeometryMultiPatch";
    2766          66 :                 break;
    2767             :         }
    2768         923 :         CPLCreateXMLElementAndValue(psRoot, "ShapeType", pszShapeType);
    2769         923 :         CPLCreateXMLElementAndValue(psRoot, "ShapeFieldName",
    2770         923 :                                     poGeomFieldDefn->GetName().c_str());
    2771             : 
    2772         923 :         const bool bGeomTypeHasZ = CPL_TO_BOOL(OGR_GT_HasZ(m_eGeomType));
    2773         923 :         const bool bGeomTypeHasM = CPL_TO_BOOL(OGR_GT_HasM(m_eGeomType));
    2774         923 :         CPLCreateXMLElementAndValue(psRoot, "HasM",
    2775             :                                     bGeomTypeHasM ? "true" : "false");
    2776         923 :         CPLCreateXMLElementAndValue(psRoot, "HasZ",
    2777             :                                     bGeomTypeHasZ ? "true" : "false");
    2778         923 :         CPLCreateXMLElementAndValue(psRoot, "HasSpatialIndex", "false");
    2779             :         const char *pszAreaFieldName =
    2780         923 :             m_iAreaField >= 0
    2781         923 :                 ? m_poFeatureDefn->GetFieldDefn(m_iAreaField)->GetNameRef()
    2782         923 :                 : "";
    2783         923 :         CPLCreateXMLElementAndValue(psRoot, "AreaFieldName", pszAreaFieldName);
    2784             :         const char *pszLengthFieldName =
    2785         923 :             m_iLengthField >= 0
    2786         923 :                 ? m_poFeatureDefn->GetFieldDefn(m_iLengthField)->GetNameRef()
    2787         923 :                 : "";
    2788         923 :         CPLCreateXMLElementAndValue(psRoot, "LengthFieldName",
    2789             :                                     pszLengthFieldName);
    2790             : 
    2791         923 :         XMLSerializeGeomFieldBase(psRoot, poGeomFieldDefn, GetSpatialRef());
    2792             :     }
    2793             : 
    2794        1139 :     char *pszDefinition = CPLSerializeXMLTree(oTree.get());
    2795        1139 :     m_osDefinition = pszDefinition;
    2796        1139 :     CPLFree(pszDefinition);
    2797             : }
    2798             : 
    2799             : /************************************************************************/
    2800             : /*                           RegisterTable()                            */
    2801             : /************************************************************************/
    2802             : 
    2803         327 : bool OGROpenFileGDBLayer::RegisterTable()
    2804             : {
    2805         327 :     m_bRegisteredTable = true;
    2806             : 
    2807         327 :     CPLAssert(!m_osThisGUID.empty());
    2808             : 
    2809             :     const char *pszFeatureDataset =
    2810         327 :         m_aosCreationOptions.FetchNameValue("FEATURE_DATASET");
    2811         327 :     if (pszFeatureDataset)
    2812             :     {
    2813          14 :         if (!m_poDS->RegisterInItemRelationships(
    2814           7 :                 m_osFeatureDatasetGUID, m_osThisGUID,
    2815             :                 pszDatasetInFeatureDatasetUUID))
    2816             :         {
    2817           0 :             return false;
    2818             :         }
    2819             :     }
    2820             :     else
    2821             :     {
    2822         640 :         if (!m_poDS->RegisterInItemRelationships(m_poDS->m_osRootGUID,
    2823         320 :                                                  m_osThisGUID,
    2824             :                                                  // DatasetInFolder
    2825             :                                                  pszDatasetInFolderUUID))
    2826             :         {
    2827           0 :             return false;
    2828             :         }
    2829             :     }
    2830             : 
    2831         327 :     if (m_eGeomType != wkbNone)
    2832             :     {
    2833         240 :         return m_poDS->RegisterFeatureClassInItems(
    2834         240 :             m_osThisGUID, m_osName, m_osPath, m_poLyrTable,
    2835         240 :             m_osDefinition.c_str(), m_osDocumentation.c_str());
    2836             :     }
    2837             :     else
    2838             :     {
    2839          87 :         return m_poDS->RegisterASpatialTableInItems(
    2840          87 :             m_osThisGUID, m_osName, m_osPath, m_osDefinition.c_str(),
    2841          87 :             m_osDocumentation.c_str());
    2842             :     }
    2843             : }
    2844             : 
    2845             : /************************************************************************/
    2846             : /*                             SyncToDisk()                             */
    2847             : /************************************************************************/
    2848             : 
    2849       11956 : OGRErr OGROpenFileGDBLayer::SyncToDisk()
    2850             : {
    2851       11956 :     if (!m_bEditable || m_poLyrTable == nullptr)
    2852       10959 :         return OGRERR_NONE;
    2853             : 
    2854         997 :     if (!m_bRegisteredTable && !RegisterTable())
    2855           0 :         return OGRERR_FAILURE;
    2856             : 
    2857         997 :     return m_poLyrTable->Sync() ? OGRERR_NONE : OGRERR_FAILURE;
    2858             : }
    2859             : 
    2860             : /************************************************************************/
    2861             : /*                         CreateSpatialIndex()                         */
    2862             : /************************************************************************/
    2863             : 
    2864           0 : void OGROpenFileGDBLayer::CreateSpatialIndex()
    2865             : {
    2866           0 :     if (!m_bEditable)
    2867           0 :         return;
    2868             : 
    2869           0 :     if (!BuildLayerDefinition())
    2870           0 :         return;
    2871             : 
    2872           0 :     m_poLyrTable->CreateSpatialIndex();
    2873             : }
    2874             : 
    2875             : /************************************************************************/
    2876             : /*                            CreateIndex()                             */
    2877             : /************************************************************************/
    2878             : 
    2879          19 : void OGROpenFileGDBLayer::CreateIndex(const std::string &osIdxName,
    2880             :                                       const std::string &osExpression)
    2881             : {
    2882          19 :     if (!m_bEditable)
    2883           1 :         return;
    2884             : 
    2885          19 :     if (!BuildLayerDefinition())
    2886           0 :         return;
    2887             : 
    2888          19 :     const auto wIdxName = StringToWString(osIdxName);
    2889          19 :     if (EscapeReservedKeywords(wIdxName) != wIdxName)
    2890             :     {
    2891           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    2892             :                  "Invalid index name: must not be a reserved keyword");
    2893           1 :         return;
    2894             :     }
    2895             : 
    2896          18 :     m_poLyrTable->CreateIndex(osIdxName, osExpression);
    2897             : }
    2898             : 
    2899             : /************************************************************************/
    2900             : /*                               Repack()                               */
    2901             : /************************************************************************/
    2902             : 
    2903           6 : bool OGROpenFileGDBLayer::Repack(GDALProgressFunc pfnProgress,
    2904             :                                  void *pProgressData)
    2905             : {
    2906           6 :     if (!m_bEditable)
    2907           0 :         return false;
    2908             : 
    2909           6 :     if (!BuildLayerDefinition())
    2910           0 :         return false;
    2911             : 
    2912           6 :     return m_poLyrTable->Repack(pfnProgress, pProgressData);
    2913             : }
    2914             : 
    2915             : /************************************************************************/
    2916             : /*                          RecomputeExtent()                           */
    2917             : /************************************************************************/
    2918             : 
    2919           2 : void OGROpenFileGDBLayer::RecomputeExtent()
    2920             : {
    2921           2 :     if (!m_bEditable)
    2922           0 :         return;
    2923             : 
    2924           2 :     if (!BuildLayerDefinition())
    2925           0 :         return;
    2926             : 
    2927           2 :     m_poLyrTable->RecomputeExtent();
    2928             : }
    2929             : 
    2930             : /************************************************************************/
    2931             : /*                      CheckFreeListConsistency()                      */
    2932             : /************************************************************************/
    2933             : 
    2934           8 : bool OGROpenFileGDBLayer::CheckFreeListConsistency()
    2935             : {
    2936           8 :     if (!BuildLayerDefinition())
    2937           0 :         return false;
    2938             : 
    2939           8 :     return m_poLyrTable->CheckFreeListConsistency();
    2940             : }
    2941             : 
    2942             : /************************************************************************/
    2943             : /*                      BeginEmulatedTransaction()                      */
    2944             : /************************************************************************/
    2945             : 
    2946           8 : bool OGROpenFileGDBLayer::BeginEmulatedTransaction()
    2947             : {
    2948           8 :     if (!BuildLayerDefinition())
    2949           0 :         return false;
    2950             : 
    2951           8 :     if (SyncToDisk() != OGRERR_NONE)
    2952           0 :         return false;
    2953             : 
    2954           8 :     bool bRet = true;
    2955             : 
    2956          16 :     const std::string osThisDirname = CPLGetPathSafe(m_osGDBFilename.c_str());
    2957             :     const std::string osThisBasename =
    2958           8 :         CPLGetBasenameSafe(m_osGDBFilename.c_str());
    2959           8 :     char **papszFiles = VSIReadDir(osThisDirname.c_str());
    2960         193 :     for (char **papszIter = papszFiles;
    2961         193 :          papszIter != nullptr && *papszIter != nullptr; ++papszIter)
    2962             :     {
    2963         370 :         const std::string osBasename = CPLGetBasenameSafe(*papszIter);
    2964         185 :         if (osBasename == osThisBasename)
    2965             :         {
    2966             :             const std::string osDestFilename = CPLFormFilenameSafe(
    2967          48 :                 m_poDS->GetBackupDirName().c_str(), *papszIter, nullptr);
    2968             :             const std::string osSourceFilename =
    2969          48 :                 CPLFormFilenameSafe(osThisDirname.c_str(), *papszIter, nullptr);
    2970          24 :             if (CPLCopyFile(osDestFilename.c_str(), osSourceFilename.c_str()) !=
    2971             :                 0)
    2972             :             {
    2973           0 :                 bRet = false;
    2974             :             }
    2975             :         }
    2976             :     }
    2977           8 :     CSLDestroy(papszFiles);
    2978             : 
    2979           8 :     m_bHasCreatedBackupForTransaction = true;
    2980             : 
    2981           8 :     m_poFeatureDefnBackup.reset(m_poFeatureDefn->Clone());
    2982             : 
    2983           8 :     return bRet;
    2984             : }
    2985             : 
    2986             : /************************************************************************/
    2987             : /*                     CommitEmulatedTransaction()                      */
    2988             : /************************************************************************/
    2989             : 
    2990           3 : bool OGROpenFileGDBLayer::CommitEmulatedTransaction()
    2991             : {
    2992           3 :     m_poFeatureDefnBackup.reset();
    2993             : 
    2994           3 :     m_bHasCreatedBackupForTransaction = false;
    2995           3 :     return true;
    2996             : }
    2997             : 
    2998             : /************************************************************************/
    2999             : /*                    RollbackEmulatedTransaction()                     */
    3000             : /************************************************************************/
    3001             : 
    3002           6 : bool OGROpenFileGDBLayer::RollbackEmulatedTransaction()
    3003             : {
    3004           6 :     if (!m_bHasCreatedBackupForTransaction)
    3005           2 :         return true;
    3006             : 
    3007           4 :     SyncToDisk();
    3008             : 
    3009             :     // Restore feature definition
    3010           8 :     if (m_poFeatureDefnBackup != nullptr &&
    3011           4 :         !m_poFeatureDefn->IsSame(m_poFeatureDefnBackup.get()))
    3012             :     {
    3013           2 :         auto oTemporaryUnsealer(m_poFeatureDefn->GetTemporaryUnsealer());
    3014             :         {
    3015           1 :             const int nFieldCount = m_poFeatureDefn->GetFieldCount();
    3016           2 :             for (int i = nFieldCount - 1; i >= 0; i--)
    3017           1 :                 m_poFeatureDefn->DeleteFieldDefn(i);
    3018             :         }
    3019             :         {
    3020           1 :             const int nFieldCount = m_poFeatureDefnBackup->GetFieldCount();
    3021           3 :             for (int i = 0; i < nFieldCount; i++)
    3022           4 :                 m_poFeatureDefn->AddFieldDefn(
    3023           2 :                     m_poFeatureDefnBackup->GetFieldDefn(i));
    3024             :         }
    3025             :     }
    3026           4 :     m_poFeatureDefnBackup.reset();
    3027             : 
    3028           4 :     Close();
    3029             : 
    3030           4 :     bool bRet = true;
    3031             : 
    3032           8 :     const std::string osThisDirname = CPLGetPathSafe(m_osGDBFilename.c_str());
    3033             :     const std::string osThisBasename =
    3034           4 :         CPLGetBasenameSafe(m_osGDBFilename.c_str());
    3035             : 
    3036             :     // Delete files in working directory that match our basename
    3037             :     {
    3038           4 :         char **papszFiles = VSIReadDir(osThisDirname.c_str());
    3039          93 :         for (char **papszIter = papszFiles;
    3040          93 :              papszIter != nullptr && *papszIter != nullptr; ++papszIter)
    3041             :         {
    3042         178 :             const std::string osBasename = CPLGetBasenameSafe(*papszIter);
    3043          89 :             if (osBasename == osThisBasename)
    3044             :             {
    3045             :                 const std::string osDestFilename = CPLFormFilenameSafe(
    3046          18 :                     osThisDirname.c_str(), *papszIter, nullptr);
    3047           9 :                 VSIUnlink(osDestFilename.c_str());
    3048             :             }
    3049             :         }
    3050           4 :         CSLDestroy(papszFiles);
    3051             :     }
    3052             : 
    3053             :     // Restore backup files
    3054           4 :     bool bBackupFound = false;
    3055             :     {
    3056           4 :         char **papszFiles = VSIReadDir(m_poDS->GetBackupDirName().c_str());
    3057          66 :         for (char **papszIter = papszFiles;
    3058          66 :              papszIter != nullptr && *papszIter != nullptr; ++papszIter)
    3059             :         {
    3060         124 :             const std::string osBasename = CPLGetBasenameSafe(*papszIter);
    3061          62 :             if (osBasename == osThisBasename)
    3062             :             {
    3063          12 :                 bBackupFound = true;
    3064             :                 const std::string osDestFilename = CPLFormFilenameSafe(
    3065          24 :                     osThisDirname.c_str(), *papszIter, nullptr);
    3066             :                 const std::string osSourceFilename = CPLFormFilenameSafe(
    3067          24 :                     m_poDS->GetBackupDirName().c_str(), *papszIter, nullptr);
    3068          12 :                 if (CPLCopyFile(osDestFilename.c_str(),
    3069          12 :                                 osSourceFilename.c_str()) != 0)
    3070             :                 {
    3071           0 :                     bRet = false;
    3072             :                 }
    3073             :             }
    3074             :         }
    3075           4 :         CSLDestroy(papszFiles);
    3076             :     }
    3077             : 
    3078           4 :     if (bBackupFound)
    3079             :     {
    3080           4 :         m_poLyrTable = new FileGDBTable();
    3081           4 :         if (m_poLyrTable->Open(m_osGDBFilename, m_bEditable, GetDescription()))
    3082             :         {
    3083           4 :             if (m_iGeomFieldIdx >= 0)
    3084             :             {
    3085           1 :                 m_iGeomFieldIdx = m_poLyrTable->GetGeomFieldIdx();
    3086           1 :                 if (m_iGeomFieldIdx < 0)
    3087             :                 {
    3088           0 :                     Close();
    3089           0 :                     bRet = false;
    3090             :                 }
    3091             :                 else
    3092             :                 {
    3093           1 :                     m_bValidLayerDefn = TRUE;
    3094             :                 }
    3095             :             }
    3096             :             else
    3097             :             {
    3098           3 :                 m_bValidLayerDefn = TRUE;
    3099             :             }
    3100             :         }
    3101             :         else
    3102             :         {
    3103           0 :             Close();
    3104           0 :             bRet = false;
    3105             :         }
    3106             :     }
    3107             : 
    3108           4 :     m_bHasCreatedBackupForTransaction = false;
    3109             : 
    3110           4 :     delete m_poAttributeIterator;
    3111           4 :     m_poAttributeIterator = nullptr;
    3112             : 
    3113           4 :     delete m_poIterMinMax;
    3114           4 :     m_poIterMinMax = nullptr;
    3115             : 
    3116           4 :     delete m_poSpatialIndexIterator;
    3117           4 :     m_poSpatialIndexIterator = nullptr;
    3118             : 
    3119           4 :     delete m_poCombinedIterator;
    3120           4 :     m_poCombinedIterator = nullptr;
    3121             : 
    3122           4 :     if (m_pQuadTree != nullptr)
    3123           0 :         CPLQuadTreeDestroy(m_pQuadTree);
    3124           4 :     m_pQuadTree = nullptr;
    3125             : 
    3126           4 :     CPLFree(m_pahFilteredFeatures);
    3127           4 :     m_pahFilteredFeatures = nullptr;
    3128             : 
    3129           4 :     m_nFilteredFeatureCount = -1;
    3130             : 
    3131           4 :     m_eSpatialIndexState = SPI_INVALID;
    3132             : 
    3133           4 :     if (m_poLyrTable && m_iGeomFieldIdx >= 0)
    3134             :     {
    3135           1 :         m_poGeomConverter.reset(FileGDBOGRGeometryConverter::BuildConverter(
    3136           1 :             m_poLyrTable->GetGeomField()));
    3137             :     }
    3138             : 
    3139           4 :     return bRet;
    3140             : }
    3141             : 
    3142             : /************************************************************************/
    3143             : /*                               Rename()                               */
    3144             : /************************************************************************/
    3145             : 
    3146          10 : OGRErr OGROpenFileGDBLayer::Rename(const char *pszDstTableName)
    3147             : {
    3148          10 :     if (!m_bEditable)
    3149           0 :         return OGRERR_FAILURE;
    3150             : 
    3151          10 :     if (!BuildLayerDefinition())
    3152           0 :         return OGRERR_FAILURE;
    3153             : 
    3154          10 :     if (SyncToDisk() != OGRERR_NONE)
    3155           0 :         return OGRERR_FAILURE;
    3156             : 
    3157          10 :     if (m_poDS->IsInTransaction() &&
    3158           0 :         ((!m_bHasCreatedBackupForTransaction && !BeginEmulatedTransaction()) ||
    3159           0 :          !m_poDS->BackupSystemTablesForTransaction()))
    3160             :     {
    3161           0 :         return OGRERR_FAILURE;
    3162             :     }
    3163             : 
    3164          30 :     const std::string osLaunderedName(GetLaunderedLayerName(pszDstTableName));
    3165          10 :     if (pszDstTableName != osLaunderedName)
    3166             :     {
    3167           6 :         CPLError(CE_Failure, CPLE_AppDefined,
    3168             :                  "%s is not a valid layer name. %s would be a valid one.",
    3169             :                  pszDstTableName, osLaunderedName.c_str());
    3170           6 :         return OGRERR_FAILURE;
    3171             :     }
    3172             : 
    3173           4 :     if (m_poDS->GetLayerByName(pszDstTableName) != nullptr)
    3174             :     {
    3175           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Layer %s already exists",
    3176             :                  pszDstTableName);
    3177           0 :         return OGRERR_FAILURE;
    3178             :     }
    3179             : 
    3180           8 :     const std::string osOldName(m_osName);
    3181             : 
    3182           4 :     m_osName = pszDstTableName;
    3183           4 :     SetDescription(pszDstTableName);
    3184           4 :     whileUnsealing(m_poFeatureDefn)->SetName(pszDstTableName);
    3185             : 
    3186           4 :     auto nLastSlashPos = m_osPath.rfind('\\');
    3187           4 :     if (nLastSlashPos != std::string::npos)
    3188             :     {
    3189           4 :         m_osPath.resize(nLastSlashPos + 1);
    3190             :     }
    3191             :     else
    3192             :     {
    3193           0 :         m_osPath = '\\';
    3194             :     }
    3195           4 :     m_osPath += m_osName;
    3196             : 
    3197           4 :     RefreshXMLDefinitionInMemory();
    3198             : 
    3199             :     // Update GDB_SystemCatalog
    3200             :     {
    3201           4 :         FileGDBTable oTable;
    3202           4 :         if (!oTable.Open(m_poDS->m_osGDBSystemCatalogFilename.c_str(), true))
    3203           0 :             return OGRERR_FAILURE;
    3204             : 
    3205           4 :         FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, OGRERR_FAILURE);
    3206             : 
    3207          40 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    3208             :              ++iCurFeat)
    3209             :         {
    3210          40 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    3211          40 :             if (iCurFeat < 0)
    3212           0 :                 break;
    3213          40 :             const auto psName = oTable.GetFieldValue(iName);
    3214          40 :             if (psName && psName->String == osOldName)
    3215             :             {
    3216           4 :                 auto asFields = oTable.GetAllFieldValues();
    3217             : 
    3218           4 :                 CPLFree(asFields[iName].String);
    3219           4 :                 asFields[iName].String = CPLStrdup(m_osName.c_str());
    3220             : 
    3221             :                 bool bRet =
    3222           8 :                     oTable.UpdateFeature(iCurFeat + 1, asFields, nullptr) &&
    3223           4 :                     oTable.Sync();
    3224           4 :                 oTable.FreeAllFieldValues(asFields);
    3225           4 :                 if (!bRet)
    3226           0 :                     return OGRERR_FAILURE;
    3227           4 :                 break;
    3228             :             }
    3229             :         }
    3230             :     }
    3231             : 
    3232             :     // Update GDB_Items
    3233             :     {
    3234           4 :         FileGDBTable oTable;
    3235           4 :         if (!oTable.Open(m_poDS->m_osGDBItemsFilename.c_str(), true))
    3236           0 :             return OGRERR_FAILURE;
    3237             : 
    3238           4 :         FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, OGRERR_FAILURE);
    3239           4 :         FETCH_FIELD_IDX_WITH_RET(iPath, "Path", FGFT_STRING, OGRERR_FAILURE);
    3240           4 :         FETCH_FIELD_IDX_WITH_RET(iPhysicalName, "PhysicalName", FGFT_STRING,
    3241             :                                  OGRERR_FAILURE);
    3242           4 :         FETCH_FIELD_IDX_WITH_RET(iDefinition, "Definition", FGFT_XML,
    3243             :                                  OGRERR_FAILURE);
    3244             : 
    3245          18 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    3246             :              ++iCurFeat)
    3247             :         {
    3248          18 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    3249          18 :             if (iCurFeat < 0)
    3250           0 :                 break;
    3251          18 :             const auto psName = oTable.GetFieldValue(iName);
    3252          18 :             if (psName && psName->String == osOldName)
    3253             :             {
    3254           4 :                 auto asFields = oTable.GetAllFieldValues();
    3255             : 
    3256           4 :                 CPLFree(asFields[iName].String);
    3257           4 :                 asFields[iName].String = CPLStrdup(m_osName.c_str());
    3258             : 
    3259           8 :                 if (!OGR_RawField_IsNull(&asFields[iPath]) &&
    3260           4 :                     !OGR_RawField_IsUnset(&asFields[iPath]))
    3261             :                 {
    3262           4 :                     CPLFree(asFields[iPath].String);
    3263             :                 }
    3264           4 :                 asFields[iPath].String = CPLStrdup(m_osPath.c_str());
    3265             : 
    3266           8 :                 if (!OGR_RawField_IsNull(&asFields[iPhysicalName]) &&
    3267           4 :                     !OGR_RawField_IsUnset(&asFields[iPhysicalName]))
    3268             :                 {
    3269           4 :                     CPLFree(asFields[iPhysicalName].String);
    3270             :                 }
    3271           4 :                 CPLString osUCName(m_osName);
    3272           4 :                 osUCName.toupper();
    3273           4 :                 asFields[iPhysicalName].String = CPLStrdup(osUCName.c_str());
    3274             : 
    3275           8 :                 if (!OGR_RawField_IsNull(&asFields[iDefinition]) &&
    3276           4 :                     !OGR_RawField_IsUnset(&asFields[iDefinition]))
    3277             :                 {
    3278           4 :                     CPLFree(asFields[iDefinition].String);
    3279             :                 }
    3280           8 :                 asFields[iDefinition].String =
    3281           4 :                     CPLStrdup(m_osDefinition.c_str());
    3282             : 
    3283             :                 bool bRet =
    3284           8 :                     oTable.UpdateFeature(iCurFeat + 1, asFields, nullptr) &&
    3285           4 :                     oTable.Sync();
    3286           4 :                 oTable.FreeAllFieldValues(asFields);
    3287           4 :                 if (!bRet)
    3288           0 :                     return OGRERR_FAILURE;
    3289           4 :                 break;
    3290             :             }
    3291             :         }
    3292             :     }
    3293             : 
    3294           4 :     return OGRERR_NONE;
    3295             : }

Generated by: LCOV version 1.14