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

Generated by: LCOV version 1.14