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

Generated by: LCOV version 1.14