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-05-31 00:00:17 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        3416 : static std::wstring StringToWString(const std::string &utf8string)
      50             : {
      51             :     wchar_t *pszUTF16 =
      52        3416 :         CPLRecodeToWChar(utf8string.c_str(), CPL_ENC_UTF8, CPL_ENC_UCS2);
      53        3416 :     std::wstring utf16string = pszUTF16;
      54        3416 :     CPLFree(pszUTF16);
      55        3416 :     return utf16string;
      56             : }
      57             : 
      58             : /*************************************************************************/
      59             : /*                            WStringToString()                          */
      60             : /*************************************************************************/
      61             : 
      62        4757 : static std::string WStringToString(const std::wstring &utf16string)
      63             : {
      64             :     char *pszUTF8 =
      65        4757 :         CPLRecodeFromWChar(utf16string.c_str(), CPL_ENC_UCS2, CPL_ENC_UTF8);
      66        4757 :     std::string utf8string = pszUTF8;
      67        4757 :     CPLFree(pszUTF8);
      68        4757 :     return utf8string;
      69             : }
      70             : 
      71             : /*************************************************************************/
      72             : /*                              LaunderName()                            */
      73             : /*************************************************************************/
      74             : 
      75        1179 : static std::wstring LaunderName(const std::wstring &name)
      76             : {
      77        1179 :     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        1179 :     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        9426 :     for (size_t i = 0; i < newName.size(); i++)
      92             :     {
      93       15250 :         if (!(newName[i] == '_' || (newName[i] >= '0' && newName[i] <= '9') ||
      94        7003 :               (newName[i] >= 'a' && newName[i] <= 'z') ||
      95         686 :               (newName[i] >= 'A' && newName[i] <= 'Z') || newName[i] >= 128))
      96             :         {
      97          61 :             newName[i] = '_';
      98             :         }
      99             :     }
     100             : 
     101        1179 :     return newName;
     102             : }
     103             : 
     104             : /*************************************************************************/
     105             : /*                      EscapeUnsupportedPrefixes()                      */
     106             : /*************************************************************************/
     107             : 
     108         336 : static std::wstring EscapeUnsupportedPrefixes(const std::wstring &className)
     109             : {
     110         336 :     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        1341 :     for (int i = 0; UNSUPPORTED_PREFIXES[i] != nullptr; i++)
     117             :     {
     118             :         // cppcheck-suppress stlIfStrFind
     119        1006 :         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         336 :     return newName;
     130             : }
     131             : 
     132             : /*************************************************************************/
     133             : /*                         EscapeReservedKeywords()                      */
     134             : /*************************************************************************/
     135             : 
     136        1198 : static std::wstring EscapeReservedKeywords(const std::wstring &name)
     137             : {
     138        2396 :     std::string newName = WStringToString(name);
     139        2396 :     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       34702 :     for (const char *pszKeyword : apszRESERVED_WORDS)
     144             :     {
     145       33507 :         if (upperName == pszKeyword)
     146             :         {
     147           3 :             newName += '_';
     148           3 :             break;
     149             :         }
     150             :     }
     151             : 
     152        2396 :     return StringToWString(newName);
     153             : }
     154             : 
     155             : /***********************************************************************/
     156             : /*                     XMLSerializeGeomFieldBase()                     */
     157             : /***********************************************************************/
     158             : 
     159         929 : static void XMLSerializeGeomFieldBase(CPLXMLNode *psRoot,
     160             :                                       const FileGDBGeomField *poGeomFieldDefn,
     161             :                                       const OGRSpatialReference *poSRS)
     162             : {
     163         929 :     auto psExtent = CPLCreateXMLElementAndValue(psRoot, "Extent", "");
     164         929 :     CPLAddXMLAttributeAndValue(psExtent, "xsi:nil", "true");
     165             : 
     166             :     auto psSpatialReference =
     167         929 :         CPLCreateXMLNode(psRoot, CXT_Element, "SpatialReference");
     168             : 
     169         929 :     if (poSRS == nullptr)
     170             :     {
     171         416 :         CPLAddXMLAttributeAndValue(psSpatialReference, "xsi:type",
     172             :                                    "typens:UnknownCoordinateSystem");
     173             :     }
     174             :     else
     175             :     {
     176         513 :         if (poSRS->IsGeographic())
     177         485 :             CPLAddXMLAttributeAndValue(psSpatialReference, "xsi:type",
     178             :                                        "typens:GeographicCoordinateSystem");
     179             :         else
     180          28 :             CPLAddXMLAttributeAndValue(psSpatialReference, "xsi:type",
     181             :                                        "typens:ProjectedCoordinateSystem");
     182         513 :         CPLCreateXMLElementAndValue(psSpatialReference, "WKT",
     183         513 :                                     poGeomFieldDefn->GetWKT().c_str());
     184             :     }
     185         929 :     CPLCreateXMLElementAndValue(
     186             :         psSpatialReference, "XOrigin",
     187             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetXOrigin()));
     188         929 :     CPLCreateXMLElementAndValue(
     189             :         psSpatialReference, "YOrigin",
     190             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetYOrigin()));
     191         929 :     CPLCreateXMLElementAndValue(
     192             :         psSpatialReference, "XYScale",
     193             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetXYScale()));
     194         929 :     CPLCreateXMLElementAndValue(
     195             :         psSpatialReference, "ZOrigin",
     196             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetZOrigin()));
     197         929 :     CPLCreateXMLElementAndValue(
     198             :         psSpatialReference, "ZScale",
     199             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetZScale()));
     200         929 :     CPLCreateXMLElementAndValue(
     201             :         psSpatialReference, "MOrigin",
     202             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetMOrigin()));
     203         929 :     CPLCreateXMLElementAndValue(
     204             :         psSpatialReference, "MScale",
     205             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetMScale()));
     206         929 :     CPLCreateXMLElementAndValue(
     207             :         psSpatialReference, "XYTolerance",
     208             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetXYTolerance()));
     209         929 :     CPLCreateXMLElementAndValue(
     210             :         psSpatialReference, "ZTolerance",
     211             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetZTolerance()));
     212         929 :     CPLCreateXMLElementAndValue(
     213             :         psSpatialReference, "MTolerance",
     214             :         CPLSPrintf("%.17g", poGeomFieldDefn->GetMTolerance()));
     215         929 :     CPLCreateXMLElementAndValue(psSpatialReference, "HighPrecision", "true");
     216         929 :     if (poSRS)
     217             :     {
     218         513 :         if (CPLTestBool(CPLGetConfigOption("OPENFILEGDB_WRITE_WKID", "YES")))
     219             :         {
     220         513 :             const char *pszKey = poSRS->IsProjected() ? "PROJCS" : "GEOGCS";
     221         513 :             const char *pszAuthorityName = poSRS->GetAuthorityName(pszKey);
     222         513 :             const char *pszAuthorityCode = poSRS->GetAuthorityCode(pszKey);
     223         513 :             if (pszAuthorityName && pszAuthorityCode &&
     224         513 :                 (EQUAL(pszAuthorityName, "EPSG") ||
     225           0 :                  EQUAL(pszAuthorityName, "ESRI")))
     226             :             {
     227         513 :                 CPLCreateXMLElementAndValue(psSpatialReference, "WKID",
     228             :                                             pszAuthorityCode);
     229         513 :                 if (CPLTestBool(CPLGetConfigOption(
     230             :                         "OPENFILEGDB_WRITE_LATESTWKID", "YES")))
     231             :                 {
     232         513 :                     CPLCreateXMLElementAndValue(psSpatialReference,
     233             :                                                 "LatestWKID", pszAuthorityCode);
     234             :                 }
     235             :             }
     236             :         }
     237             : 
     238         513 :         if (poSRS->IsCompound() &&
     239           0 :             CPLTestBool(CPLGetConfigOption("OPENFILEGDB_WRITE_VCSWKID", "YES")))
     240             :         {
     241           0 :             const char *pszAuthorityName = poSRS->GetAuthorityName("VERT_CS");
     242           0 :             const char *pszAuthorityCode = poSRS->GetAuthorityCode("VERT_CS");
     243           0 :             if (pszAuthorityName && pszAuthorityCode &&
     244           0 :                 (EQUAL(pszAuthorityName, "EPSG") ||
     245           0 :                  EQUAL(pszAuthorityName, "ESRI")))
     246             :             {
     247           0 :                 CPLCreateXMLElementAndValue(psSpatialReference, "VCSWKID",
     248             :                                             pszAuthorityCode);
     249           0 :                 if (CPLTestBool(CPLGetConfigOption(
     250             :                         "OPENFILEGDB_WRITE_LATESTVCSWKID", "YES")))
     251             :                 {
     252           0 :                     CPLCreateXMLElementAndValue(
     253             :                         psSpatialReference, "LatestVCSWKID", pszAuthorityCode);
     254             :                 }
     255             :             }
     256             :         }
     257             :     }
     258         929 : }
     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         336 : OGROpenFileGDBLayer::GetLaunderedLayerName(const std::string &osNameOri) const
     336             : {
     337         672 :     std::wstring wlayerName = StringToWString(osNameOri);
     338             : 
     339         336 :     wlayerName = LaunderName(wlayerName);
     340         336 :     wlayerName = EscapeReservedKeywords(wlayerName);
     341         336 :     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         336 :     constexpr size_t TABLE_NAME_MAX_SIZE = 160;
     348         336 :     if (wlayerName.size() > TABLE_NAME_MAX_SIZE)
     349           4 :         wlayerName.resize(TABLE_NAME_MAX_SIZE);
     350             : 
     351             :     /* Ensures uniqueness of layer name */
     352         336 :     int numRenames = 1;
     353         686 :     while ((m_poDS->GetLayerByName(WStringToString(wlayerName).c_str()) !=
     354        1029 :             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         672 :     while ((m_poDS->GetLayerByName(WStringToString(wlayerName).c_str()) !=
     365        1008 :             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         672 :     return WStringToString(wlayerName);
     377             : }
     378             : 
     379             : /***********************************************************************/
     380             : /*                            Create()                                 */
     381             : /***********************************************************************/
     382             : 
     383         328 : bool OGROpenFileGDBLayer::Create(const OGRGeomFieldDefn *poSrcGeomFieldDefn)
     384             : {
     385         328 :     FileGDBTableGeometryType eTableGeomType = FGTGT_NONE;
     386         328 :     const auto eFlattenType = wkbFlatten(OGR_GT_GetLinear(m_eGeomType));
     387         328 :     if (eFlattenType == wkbNone)
     388          87 :         eTableGeomType = FGTGT_NONE;
     389         241 :     else if (CPLTestBool(m_aosCreationOptions.FetchNameValueDef(
     390             :                  "CREATE_MULTIPATCH", "FALSE")))
     391             :     {
     392             :         // For compatibility with FileGDB driver
     393           2 :         eTableGeomType = FGTGT_MULTIPATCH;
     394             :     }
     395         239 :     else if (eFlattenType == wkbPoint)
     396         102 :         eTableGeomType = FGTGT_POINT;
     397         137 :     else if (eFlattenType == wkbMultiPoint)
     398          15 :         eTableGeomType = FGTGT_MULTIPOINT;
     399         239 :     else if (OGR_GT_IsCurve(eFlattenType) ||
     400         117 :              OGR_GT_IsSubClassOf(eFlattenType, wkbMultiCurve))
     401             :     {
     402          47 :         eTableGeomType = FGTGT_LINE;
     403             :     }
     404          75 :     else if (wkbFlatten(m_eGeomType) == wkbTIN ||
     405          72 :              wkbFlatten(m_eGeomType) == wkbPolyhedralSurface ||
     406         216 :              m_eGeomType == wkbGeometryCollection25D ||
     407          69 :              m_eGeomType == wkbSetZ(wkbUnknown))
     408             :     {
     409           6 :         eTableGeomType = FGTGT_MULTIPATCH;
     410             :     }
     411         129 :     else if (OGR_GT_IsSurface(eFlattenType) ||
     412          60 :              OGR_GT_IsSubClassOf(eFlattenType, wkbMultiSurface))
     413             :     {
     414          67 :         eTableGeomType = FGTGT_POLYGON;
     415             :     }
     416             :     else
     417             :     {
     418           2 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported geometry type");
     419           2 :         return false;
     420             :     }
     421             : 
     422         652 :     const std::string osNameOri(m_osName);
     423             :     /* Launder the Layer name */
     424         326 :     m_osName = GetLaunderedLayerName(osNameOri);
     425         326 :     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         326 :         m_aosCreationOptions.FetchNameValue("FEATURE_DATASET");
     434         652 :     std::string osFeatureDatasetDef;
     435         326 :     std::unique_ptr<OGRSpatialReference> poFeatureDatasetSRS;
     436         326 :     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         326 :     m_poFeatureDefn =
     497         326 :         new OGROpenFileGDBFeatureDefn(this, m_osName.c_str(), true);
     498         326 :     SetDescription(m_poFeatureDefn->GetName());
     499         326 :     m_poFeatureDefn->SetGeomType(wkbNone);
     500         326 :     m_poFeatureDefn->Reference();
     501             : 
     502         326 :     m_osThisGUID = OFGDBGenerateUUID();
     503             : 
     504         326 :     m_bValidLayerDefn = true;
     505         326 :     m_bEditable = true;
     506         326 :     m_bRegisteredTable = false;
     507         326 :     m_bTimeInUTC = CPLTestBool(
     508             :         m_aosCreationOptions.FetchNameValueDef("TIME_IN_UTC", "YES"));
     509             : 
     510         326 :     int nTablxOffsetSize = 5;
     511         326 :     bool bTextUTF16 = false;
     512             :     const char *pszConfigurationKeyword =
     513         326 :         m_aosCreationOptions.FetchNameValue("CONFIGURATION_KEYWORD");
     514         326 :     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         326 :     m_osPath = '\\';
     541         326 :     if (pszFeatureDataset)
     542             :     {
     543           7 :         m_osPath += pszFeatureDataset;
     544           7 :         m_osPath += '\\';
     545             :     }
     546         326 :     m_osPath += m_osName;
     547             : 
     548             :     const char *pszDocumentation =
     549         326 :         m_aosCreationOptions.FetchNameValue("DOCUMENTATION");
     550         326 :     if (pszDocumentation)
     551           1 :         m_osDocumentation = pszDocumentation;
     552             : 
     553         326 :     const bool bGeomTypeHasZ = CPL_TO_BOOL(OGR_GT_HasZ(m_eGeomType));
     554         326 :     const bool bGeomTypeHasM = CPL_TO_BOOL(OGR_GT_HasM(m_eGeomType));
     555             : 
     556         326 :     m_poLyrTable = new FileGDBTable();
     557         326 :     if (!m_poLyrTable->Create(m_osGDBFilename.c_str(), nTablxOffsetSize,
     558             :                               eTableGeomType, bGeomTypeHasZ, bGeomTypeHasM))
     559             :     {
     560           0 :         Close();
     561           0 :         return false;
     562             :     }
     563         326 :     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         326 :     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         326 :     if (m_eGeomType != wkbNone && poSrcGeomFieldDefn)
     581             :     {
     582         239 :         const auto poSRS = poSrcGeomFieldDefn->GetSpatialRef();
     583             : 
     584             :         auto poGeomFieldDefn = std::make_unique<OGROpenFileGDBGeomFieldDefn>(
     585             :             this,
     586           0 :             m_aosCreationOptions.FetchNameValueDef("GEOMETRY_NAME", "SHAPE"),
     587         239 :             m_eGeomType);
     588         239 :         poGeomFieldDefn->SetNullable(
     589         239 :             CPLTestBool(m_aosCreationOptions.FetchNameValueDef(
     590             :                 "GEOMETRY_NULLABLE", "YES")));
     591             : 
     592         239 :         if (poSRS)
     593             :         {
     594          54 :             const char *const apszOptions[] = {
     595             :                 "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr};
     596          56 :             if (poFeatureDatasetSRS &&
     597          56 :                 !poSRS->IsSame(poFeatureDatasetSRS.get(), apszOptions))
     598             :             {
     599           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     600             :                          "Layer CRS does not match feature dataset CRS");
     601           1 :                 return false;
     602             :             }
     603             : 
     604          53 :             auto poSRSClone = poSRS->Clone();
     605          53 :             poGeomFieldDefn->SetSpatialRef(poSRSClone);
     606          53 :             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         238 :         std::string osWKT;
     616         238 :         if (poSRS)
     617             :         {
     618          53 :             const char *const apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
     619             :             char *pszWKT;
     620          53 :             poSRS->exportToWkt(&pszWKT, apszOptions);
     621          53 :             osWKT = pszWKT;
     622          53 :             CPLFree(pszWKT);
     623             :         }
     624             :         else
     625             :         {
     626         185 :             osWKT = "{B286C06B-0879-11D2-AACA-00C04FA33C20}";
     627             :         }
     628             : 
     629             :         const auto oCoordPrec = GDBGridSettingsFromOGR(
     630         238 :             poSrcGeomFieldDefn, m_aosCreationOptions.List());
     631         238 :         poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec);
     632             :         const auto &oGridsOptions =
     633         238 :             oCoordPrec.oFormatSpecificOptions.find("FileGeodatabase")->second;
     634             :         const double dfXOrigin =
     635         238 :             CPLAtof(oGridsOptions.FetchNameValue("XOrigin"));
     636             :         const double dfYOrigin =
     637         238 :             CPLAtof(oGridsOptions.FetchNameValue("YOrigin"));
     638             :         const double dfXYScale =
     639         238 :             CPLAtof(oGridsOptions.FetchNameValue("XYScale"));
     640             :         const double dfXYTolerance =
     641         238 :             CPLAtof(oGridsOptions.FetchNameValue("XYTolerance"));
     642             :         const double dfZOrigin =
     643         238 :             CPLAtof(oGridsOptions.FetchNameValue("ZOrigin"));
     644         238 :         const double dfZScale = CPLAtof(oGridsOptions.FetchNameValue("ZScale"));
     645             :         const double dfZTolerance =
     646         238 :             CPLAtof(oGridsOptions.FetchNameValue("ZTolerance"));
     647             :         const double dfMOrigin =
     648         238 :             CPLAtof(oGridsOptions.FetchNameValue("MOrigin"));
     649         238 :         const double dfMScale = CPLAtof(oGridsOptions.FetchNameValue("MScale"));
     650             :         const double dfMTolerance =
     651         238 :             CPLAtof(oGridsOptions.FetchNameValue("MTolerance"));
     652             : 
     653         238 :         if (!m_poDS->GetExistingSpatialRef(
     654             :                 osWKT, dfXOrigin, dfYOrigin, dfXYScale, dfZOrigin, dfZScale,
     655             :                 dfMOrigin, dfMScale, dfXYTolerance, dfZTolerance, dfMTolerance))
     656             :         {
     657         183 :             m_poDS->AddNewSpatialRef(osWKT, dfXOrigin, dfYOrigin, dfXYScale,
     658             :                                      dfZOrigin, dfZScale, dfMOrigin, dfMScale,
     659             :                                      dfXYTolerance, dfZTolerance, dfMTolerance);
     660             :         }
     661             : 
     662             :         // Will be patched later
     663         238 :         constexpr double dfSpatialGridResolution = 0;
     664             :         auto poTableGeomField = std::make_unique<FileGDBGeomField>(
     665         476 :             poGeomFieldDefn->GetNameRef(),
     666           0 :             std::string(),  // alias
     667         476 :             CPL_TO_BOOL(poGeomFieldDefn->IsNullable()), osWKT, dfXOrigin,
     668             :             dfYOrigin, dfXYScale, dfXYTolerance,
     669         714 :             std::vector<double>{dfSpatialGridResolution});
     670         238 :         poTableGeomField->SetZOriginScaleTolerance(dfZOrigin, dfZScale,
     671             :                                                    dfZTolerance);
     672         238 :         poTableGeomField->SetMOriginScaleTolerance(dfMOrigin, dfMScale,
     673             :                                                    dfMTolerance);
     674             : 
     675         238 :         if (!m_poLyrTable->CreateField(std::move(poTableGeomField)))
     676             :         {
     677           0 :             Close();
     678           0 :             return false;
     679             :         }
     680             : 
     681         238 :         m_iGeomFieldIdx = m_poLyrTable->GetGeomFieldIdx();
     682         238 :         m_poGeomConverter.reset(FileGDBOGRGeometryConverter::BuildConverter(
     683         238 :             m_poLyrTable->GetGeomField()));
     684             : 
     685         238 :         m_poFeatureDefn->AddGeomFieldDefn(std::move(poGeomFieldDefn));
     686             :     }
     687             : 
     688             :     const std::string osFIDName =
     689         650 :         m_aosCreationOptions.FetchNameValueDef("FID", "OBJECTID");
     690         325 :     if (!m_poLyrTable->CreateField(std::make_unique<FileGDBField>(
     691         325 :             osFIDName, std::string(), FGFT_OBJECTID,
     692           0 :             /* bNullable = */ false,
     693           0 :             /* bRequired = */ true,
     694         650 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)))
     695             :     {
     696           0 :         Close();
     697           0 :         return false;
     698             :     }
     699             : 
     700             :     const bool bCreateShapeLength =
     701         439 :         (eTableGeomType == FGTGT_LINE || eTableGeomType == FGTGT_POLYGON) &&
     702         114 :         CPLTestBool(m_aosCreationOptions.FetchNameValueDef(
     703         325 :             "CREATE_SHAPE_AREA_AND_LENGTH_FIELDS", "NO"));
     704             :     // Setting a non-default value doesn't work
     705         325 :     const char *pszLengthFieldName = m_aosCreationOptions.FetchNameValueDef(
     706             :         "LENGTH_FIELD_NAME", "Shape_Length");
     707             : 
     708             :     const bool bCreateShapeArea =
     709         392 :         eTableGeomType == FGTGT_POLYGON &&
     710          67 :         CPLTestBool(m_aosCreationOptions.FetchNameValueDef(
     711         325 :             "CREATE_SHAPE_AREA_AND_LENGTH_FIELDS", "NO"));
     712             :     // Setting a non-default value doesn't work
     713             :     const char *pszAreaFieldName =
     714         325 :         m_aosCreationOptions.FetchNameValueDef("AREA_FIELD_NAME", "Shape_Area");
     715             : 
     716         325 :     m_poFeatureDefn->Seal(/* bSealFields = */ true);
     717             : 
     718         325 :     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         325 :     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         325 :     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         325 :     if (m_iGeomFieldIdx >= 0)
     745         476 :         m_poLyrTable->CreateIndex(
     746         238 :             "FDO_SHAPE", m_poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef());
     747             : 
     748         325 :     if (!m_poDS->RegisterLayerInSystemCatalog(m_osName))
     749             :     {
     750           0 :         Close();
     751           0 :         return false;
     752             :     }
     753             : 
     754         328 :     if (pszFeatureDataset != nullptr && m_osFeatureDatasetGUID.empty() &&
     755           3 :         !CreateFeatureDataset(pszFeatureDataset))
     756             :     {
     757           0 :         Close();
     758           0 :         return false;
     759             :     }
     760             : 
     761         325 :     RefreshXMLDefinitionInMemory();
     762             : 
     763         325 :     return true;
     764             : }
     765             : 
     766             : /************************************************************************/
     767             : /*                       CreateXMLFieldDefinition()                     */
     768             : /************************************************************************/
     769             : 
     770        4095 : static CPLXMLNode *CreateXMLFieldDefinition(const OGRFieldDefn *poFieldDefn,
     771             :                                             const FileGDBField *poGDBFieldDefn,
     772             :                                             bool bArcGISPro32OrLater)
     773             : {
     774             :     auto GPFieldInfoEx =
     775        4095 :         CPLCreateXMLNode(nullptr, CXT_Element, "GPFieldInfoEx");
     776        4095 :     CPLAddXMLAttributeAndValue(GPFieldInfoEx, "xsi:type",
     777             :                                "typens:GPFieldInfoEx");
     778        4095 :     CPLCreateXMLElementAndValue(GPFieldInfoEx, "Name",
     779        4095 :                                 poGDBFieldDefn->GetName().c_str());
     780        4095 :     if (!poGDBFieldDefn->GetAlias().empty())
     781             :     {
     782          18 :         CPLCreateXMLElementAndValue(GPFieldInfoEx, "AliasName",
     783          18 :                                     poGDBFieldDefn->GetAlias().c_str());
     784             :     }
     785        4095 :     const auto *psDefault = poGDBFieldDefn->GetDefault();
     786        4095 :     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        4095 :     const char *pszFieldType = "";
     872        4095 :     CPL_IGNORE_RET_VAL(pszFieldType);  // Make CSA happy
     873        4095 :     int nLength = 0;
     874        4095 :     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         424 :         case FGFT_FLOAT64:
     892         424 :             nLength = 8;
     893         424 :             pszFieldType = "esriFieldTypeDouble";
     894         424 :             break;
     895         803 :         case FGFT_STRING:
     896         803 :             nLength = poGDBFieldDefn->GetMaxWidth();
     897         803 :             pszFieldType = "esriFieldTypeString";
     898         803 :             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           5 :         case FGFT_INT64:
     925           5 :             nLength = 8;
     926           5 :             pszFieldType = "esriFieldTypeBigInteger";
     927           5 :             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        4095 :         CPLCreateXMLElementAndValue(GPFieldInfoEx, "FieldType", pszFieldType);
     943        4095 :     if (!bArcGISPro32OrLater)
     944             :     {
     945        4062 :         CPLAddXMLAttributeAndValue(psFieldType, "xmlns:typens",
     946             :                                    "http://www.esri.com/schemas/ArcGIS/10.3");
     947             :     }
     948        4095 :     if (poGDBFieldDefn->IsNullable())
     949             :     {
     950        3890 :         CPLCreateXMLElementAndValue(GPFieldInfoEx, "IsNullable", "true");
     951             :     }
     952        4095 :     if (poGDBFieldDefn->IsRequired())
     953             :     {
     954          51 :         CPLCreateXMLElementAndValue(GPFieldInfoEx, "Required", "true");
     955             :     }
     956        4095 :     if (!poGDBFieldDefn->IsEditable())
     957             :     {
     958          47 :         CPLCreateXMLElementAndValue(GPFieldInfoEx, "Editable", "false");
     959             :     }
     960        4095 :     if (poGDBFieldDefn->IsHighPrecision())
     961             :     {
     962           0 :         CPLCreateXMLElementAndValue(GPFieldInfoEx, "HighPrecision", "true");
     963             :     }
     964        4095 :     CPLCreateXMLElementAndValue(GPFieldInfoEx, "Length",
     965             :                                 CPLSPrintf("%d", nLength));
     966        4095 :     CPLCreateXMLElementAndValue(GPFieldInfoEx, "Precision", "0");
     967        4095 :     CPLCreateXMLElementAndValue(GPFieldInfoEx, "Scale", "0");
     968        4095 :     if (!poFieldDefn->GetDomainName().empty())
     969             :     {
     970          10 :         CPLCreateXMLElementAndValue(GPFieldInfoEx, "DomainName",
     971          10 :                                     poFieldDefn->GetDomainName().c_str());
     972             :     }
     973        4095 :     return GPFieldInfoEx;
     974             : }
     975             : 
     976             : /************************************************************************/
     977             : /*                            GetDefault()                              */
     978             : /************************************************************************/
     979             : 
     980         843 : static bool GetDefault(const OGRFieldDefn *poField, FileGDBFieldType eType,
     981             :                        OGRField &sDefault, std::string &osDefaultVal,
     982             :                        bool bApproxOK)
     983             : {
     984         843 :     sDefault = FileGDBField::UNSET_FIELD;
     985         843 :     const char *pszDefault = poField->GetDefault();
     986         843 :     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         825 :     return true;
    1041             : }
    1042             : 
    1043             : /************************************************************************/
    1044             : /*                         GetGDBFieldType()                            */
    1045             : /************************************************************************/
    1046             : 
    1047         701 : static FileGDBFieldType GetGDBFieldType(const OGRFieldDefn *poField,
    1048             :                                         bool bArcGISPro32OrLater)
    1049             : {
    1050         701 :     FileGDBFieldType eType = FGFT_UNDEFINED;
    1051         701 :     switch (poField->GetType())
    1052             :     {
    1053         230 :         case OFTInteger:
    1054         230 :             eType =
    1055         230 :                 poField->GetSubType() == OFSTInt16 ? FGFT_INT16 : FGFT_INT32;
    1056         230 :             break;
    1057         120 :         case OFTReal:
    1058         120 :             eType = poField->GetSubType() == OFSTFloat32 ? FGFT_FLOAT32
    1059             :                                                          : FGFT_FLOAT64;
    1060         120 :             break;
    1061           9 :         case OFTInteger64:
    1062           9 :             eType = bArcGISPro32OrLater ? FGFT_INT64 : FGFT_FLOAT64;
    1063           9 :             break;
    1064         174 :         case OFTString:
    1065             :         case OFTWideString:
    1066             :         case OFTStringList:
    1067             :         case OFTWideStringList:
    1068             :         case OFTIntegerList:
    1069             :         case OFTInteger64List:
    1070             :         case OFTRealList:
    1071         174 :             eType = FGFT_STRING;
    1072         174 :             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         701 :     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         843 : OGROpenFileGDBLayer::GetLaunderedFieldName(const std::string &osNameOri) const
    1116             : {
    1117        1686 :     std::wstring osName = LaunderName(StringToWString(osNameOri));
    1118         843 :     osName = EscapeReservedKeywords(osName);
    1119             : 
    1120             :     /* Truncate to 64 characters */
    1121         843 :     constexpr size_t FIELD_NAME_MAX_SIZE = 64;
    1122         843 :     if (osName.size() > FIELD_NAME_MAX_SIZE)
    1123           2 :         osName.resize(FIELD_NAME_MAX_SIZE);
    1124             : 
    1125             :     /* Ensures uniqueness of field name */
    1126         843 :     int numRenames = 1;
    1127        1694 :     while ((m_poFeatureDefn->GetFieldIndex(WStringToString(osName).c_str()) >=
    1128        2541 :             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        1686 :     while ((m_poFeatureDefn->GetFieldIndex(WStringToString(osName).c_str()) >=
    1138        2529 :             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        1686 :     return WStringToString(osName);
    1149             : }
    1150             : 
    1151             : /************************************************************************/
    1152             : /*                            CreateField()                             */
    1153             : /************************************************************************/
    1154             : 
    1155         839 : OGRErr OGROpenFileGDBLayer::CreateField(const OGRFieldDefn *poFieldIn,
    1156             :                                         int bApproxOK)
    1157             : {
    1158         839 :     if (!m_bEditable)
    1159           0 :         return OGRERR_FAILURE;
    1160             : 
    1161         839 :     if (!BuildLayerDefinition())
    1162           0 :         return OGRERR_FAILURE;
    1163             : 
    1164         842 :     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        1678 :     OGRFieldDefn oField(poFieldIn);
    1173         839 :     OGRFieldDefn *poField = &oField;
    1174             : 
    1175         839 :     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         832 :     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         818 :     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        1678 :     const std::string osFidColumn = GetFIDColumn();
    1201        1678 :     if (!osFidColumn.empty() &&
    1202         839 :         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        1672 :     const std::string osFieldNameOri(poField->GetNameRef());
    1223        1672 :     const std::string osFieldName = GetLaunderedFieldName(osFieldNameOri);
    1224         836 :     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         836 :         m_aosCreationOptions.FetchNameValue("COLUMN_TYPES");
    1242        1672 :     std::string gdbFieldType;
    1243         836 :     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         836 :     FileGDBFieldType eType = FGFT_UNDEFINED;
    1273         836 :     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         694 :         eType = GetGDBFieldType(poField, m_bArcGISPro32OrLater);
    1311             :     }
    1312             : 
    1313         836 :     int nWidth = 0;
    1314         836 :     if (eType == FGFT_GLOBALID || eType == FGFT_GUID)
    1315             :     {
    1316          38 :         nWidth = 38;
    1317             :     }
    1318         798 :     else if (poField->GetType() == OFTString)
    1319             :     {
    1320         207 :         nWidth = poField->GetWidth();
    1321         207 :         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         182 :             nWidth = DEFAULT_STRING_WIDTH;
    1328         182 :             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         182 :             if (nWidth < DEFAULT_STRING_WIDTH)
    1340           2 :                 poField->SetWidth(nWidth);
    1341             :         }
    1342             :     }
    1343             : 
    1344         836 :     OGRField sDefault = FileGDBField::UNSET_FIELD;
    1345        1672 :     std::string osDefaultVal;
    1346         836 :     if (!GetDefault(poField, eType, sDefault, osDefaultVal,
    1347         836 :                     CPL_TO_BOOL(bApproxOK)))
    1348          12 :         return OGRERR_FAILURE;
    1349             : 
    1350         829 :     if (!poField->GetDomainName().empty() &&
    1351           5 :         (!m_osThisGUID.empty() ||
    1352         824 :          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         824 :         CPL_TO_BOOL(poField->IsNullable()) && eType != FGFT_GLOBALID;
    1362         824 :     bool bRequired = (eType == FGFT_GLOBALID);
    1363         824 :     bool bEditable = (eType != FGFT_GLOBALID);
    1364             : 
    1365         824 :     if (poField->GetType() == OFTReal)
    1366             :     {
    1367         152 :         const char *pszDefault = poField->GetDefault();
    1368         152 :         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         148 :         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         824 :     const char *pszAlias = poField->GetAlternativeNameRef();
    1384         824 :     if (!m_poLyrTable->CreateField(std::make_unique<FileGDBField>(
    1385         824 :             poField->GetNameRef(),
    1386        1648 :             pszAlias ? std::string(pszAlias) : std::string(), eType, bNullable,
    1387             :             bRequired, bEditable, nWidth, sDefault)))
    1388             :     {
    1389           9 :         return OGRERR_FAILURE;
    1390             :     }
    1391             : 
    1392         815 :     whileUnsealing(m_poFeatureDefn)->AddFieldDefn(poField);
    1393             : 
    1394         815 :     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         801 :         RefreshXMLDefinitionInMemory();
    1423             :     }
    1424             : 
    1425         815 :     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        9552 : bool OGROpenFileGDBLayer::PrepareFileGDBFeature(OGRFeature *poFeature,
    2089             :                                                 std::vector<OGRField> &fields,
    2090             :                                                 const OGRGeometry *&poGeom,
    2091             :                                                 bool bUpdate)
    2092             : {
    2093             :     // Check geometry type
    2094        9552 :     poGeom = poFeature->GetGeometryRef();
    2095             :     const auto eFlattenType =
    2096        9552 :         poGeom ? wkbFlatten(poGeom->getGeometryType()) : wkbNone;
    2097        9552 :     if (poGeom)
    2098             :     {
    2099        4126 :         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         179 :             case FGTGT_POLYGON:
    2147             :             {
    2148         179 :                 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         174 :                 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        4136 :         if (poGeom->IsEmpty() &&
    2190          27 :             !CPLTestBool(CPLGetConfigOption(
    2191             :                 "OGR_OPENFILEGDB_WRITE_EMPTY_GEOMETRY", "NO")))
    2192             :         {
    2193          15 :             poGeom = nullptr;
    2194             :         }
    2195             :     }
    2196             : 
    2197        9535 :     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        9535 :     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        9535 :     fields.resize(m_poLyrTable->GetFieldCount(), FileGDBField::UNSET_FIELD);
    2243        9535 :     m_aosTempStrings.clear();
    2244       17773 :     for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
    2245             :     {
    2246        8238 :         if (i == m_iFIDAsRegularColumnIndex)
    2247           9 :             continue;
    2248        8229 :         const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(i);
    2249             :         const int idxFileGDB =
    2250        8229 :             m_poLyrTable->GetFieldIdx(poFieldDefn->GetNameRef());
    2251        8229 :         if (idxFileGDB < 0)
    2252           0 :             continue;
    2253        8229 :         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        8081 :         memset(&fields[idxFileGDB], 0, sizeof(OGRField));
    2263        8081 :         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         351 :             case FGFT_FLOAT64:
    2280             :             {
    2281         351 :                 if (poFieldDefn->GetType() == OFTReal)
    2282             :                 {
    2283         309 :                     fields[idxFileGDB].Real =
    2284         309 :                         poFeature->GetRawFieldRef(i)->Real;
    2285             :                 }
    2286             :                 else
    2287             :                 {
    2288          42 :                     fields[idxFileGDB].Real = poFeature->GetFieldAsDouble(i);
    2289             :                 }
    2290         351 :                 break;
    2291             :             }
    2292        5933 :             case FGFT_STRING:
    2293             :             case FGFT_GUID:
    2294             :             case FGFT_XML:
    2295             :             {
    2296        5933 :                 if (poFieldDefn->GetType() == OFTString)
    2297             :                 {
    2298        5933 :                     fields[idxFileGDB].String =
    2299        5933 :                         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        5933 :                 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          22 :             case FGFT_INT64:
    2387             :             {
    2388          44 :                 fields[idxFileGDB].Integer64 =
    2389          22 :                     poFeature->GetRawFieldRef(i)->Integer64;
    2390          22 :                 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        9535 :     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        9546 : OGRErr OGROpenFileGDBLayer::ICreateFeature(OGRFeature *poFeature)
    2449             : {
    2450        9546 :     if (!m_bEditable)
    2451           0 :         return OGRERR_FAILURE;
    2452             : 
    2453        9546 :     if (!BuildLayerDefinition())
    2454           0 :         return OGRERR_FAILURE;
    2455             : 
    2456        9548 :     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        9546 :     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        9543 :     const auto nFID64Bit = poFeature->GetFID();
    2511        9543 :     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        9537 :     int nFID32Bit = (nFID64Bit > 0) ? static_cast<int>(nFID64Bit) : 0;
    2519             : 
    2520        9537 :     poFeature->FillUnsetWithDefault(FALSE, nullptr);
    2521             : 
    2522        9537 :     const OGRGeometry *poGeom = nullptr;
    2523       19074 :     std::vector<OGRField> fields;
    2524        9537 :     if (!PrepareFileGDBFeature(poFeature, fields, poGeom, /*bUpdate=*/false))
    2525          17 :         return OGRERR_FAILURE;
    2526             : 
    2527        9520 :     m_eSpatialIndexState = SPI_INVALID;
    2528        9520 :     m_nFilteredFeatureCount = -1;
    2529             : 
    2530        9520 :     if (!m_poLyrTable->CreateFeature(fields, poGeom, &nFID32Bit))
    2531           8 :         return OGRERR_FAILURE;
    2532             : 
    2533        9512 :     poFeature->SetFID(nFID32Bit);
    2534        9512 :     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        1137 : void OGROpenFileGDBLayer::RefreshXMLDefinitionInMemory()
    2626             : {
    2627        1137 :     CPLXMLTreeCloser oTree(CPLCreateXMLNode(nullptr, CXT_Element, "?xml"));
    2628        1137 :     CPLAddXMLAttributeAndValue(oTree.get(), "version", "1.0");
    2629        1137 :     CPLAddXMLAttributeAndValue(oTree.get(), "encoding", "UTF-8");
    2630             : 
    2631             :     CPLXMLNode *psRoot =
    2632        1137 :         CPLCreateXMLNode(nullptr, CXT_Element,
    2633        1137 :                          m_eGeomType == wkbNone ? "typens:DETableInfo"
    2634             :                                                 : "typens:DEFeatureClassInfo");
    2635        1137 :     CPLAddXMLSibling(oTree.get(), psRoot);
    2636             : 
    2637        1137 :     CPLAddXMLAttributeAndValue(psRoot, "xmlns:typens",
    2638        1137 :                                m_bArcGISPro32OrLater
    2639             :                                    ? "http://www.esri.com/schemas/ArcGIS/10.8"
    2640             :                                    : "http://www.esri.com/schemas/ArcGIS/10.3");
    2641        1137 :     CPLAddXMLAttributeAndValue(psRoot, "xmlns:xsi",
    2642             :                                "http://www.w3.org/2001/XMLSchema-instance");
    2643        1137 :     CPLAddXMLAttributeAndValue(psRoot, "xmlns:xs",
    2644             :                                "http://www.w3.org/2001/XMLSchema");
    2645        1137 :     CPLAddXMLAttributeAndValue(psRoot, "xsi:type",
    2646        1137 :                                m_eGeomType == wkbNone
    2647             :                                    ? "typens:DETableInfo"
    2648             :                                    : "typens:DEFeatureClassInfo");
    2649        1137 :     CPLCreateXMLElementAndValue(psRoot, "CatalogPath", m_osPath.c_str());
    2650        1137 :     CPLCreateXMLElementAndValue(psRoot, "Name", m_osName.c_str());
    2651        1137 :     CPLCreateXMLElementAndValue(psRoot, "ChildrenExpanded", "false");
    2652        1137 :     CPLCreateXMLElementAndValue(psRoot, "DatasetType",
    2653        1137 :                                 m_eGeomType == wkbNone ? "esriDTTable"
    2654             :                                                        : "esriDTFeatureClass");
    2655             : 
    2656             :     {
    2657        1137 :         FileGDBTable oTable;
    2658        1137 :         if (!oTable.Open(m_poDS->m_osGDBItemsFilename.c_str(), false))
    2659           0 :             return;
    2660        1137 :         CPLCreateXMLElementAndValue(
    2661             :             psRoot, "DSID",
    2662        1137 :             CPLSPrintf("%" PRId64, 1 + oTable.GetTotalRecordCount()));
    2663             :     }
    2664             : 
    2665        1137 :     CPLCreateXMLElementAndValue(psRoot, "Versioned", "false");
    2666        1137 :     CPLCreateXMLElementAndValue(psRoot, "CanVersion", "false");
    2667        1137 :     if (!m_osConfigurationKeyword.empty())
    2668             :     {
    2669           2 :         CPLCreateXMLElementAndValue(psRoot, "ConfigurationKeyword",
    2670             :                                     m_osConfigurationKeyword.c_str());
    2671             :     }
    2672        1137 :     if (m_bArcGISPro32OrLater)
    2673             :     {
    2674          20 :         CPLCreateXMLElementAndValue(psRoot, "RequiredGeodatabaseClientVersion",
    2675             :                                     "13.2");
    2676             :     }
    2677        1137 :     CPLCreateXMLElementAndValue(psRoot, "HasOID", "true");
    2678        1137 :     CPLCreateXMLElementAndValue(psRoot, "OIDFieldName", GetFIDColumn());
    2679             :     auto GPFieldInfoExs =
    2680        1137 :         CPLCreateXMLNode(psRoot, CXT_Element, "GPFieldInfoExs");
    2681        1137 :     CPLAddXMLAttributeAndValue(GPFieldInfoExs, "xsi:type",
    2682             :                                "typens:ArrayOfGPFieldInfoEx");
    2683             : 
    2684        7272 :     for (int i = 0; i < m_poLyrTable->GetFieldCount(); ++i)
    2685             :     {
    2686        6135 :         const auto *poGDBFieldDefn = m_poLyrTable->GetField(i);
    2687        6135 :         if (poGDBFieldDefn->GetType() == FGFT_OBJECTID)
    2688             :         {
    2689             :             auto GPFieldInfoEx =
    2690        1137 :                 CPLCreateXMLNode(GPFieldInfoExs, CXT_Element, "GPFieldInfoEx");
    2691        1137 :             CPLAddXMLAttributeAndValue(GPFieldInfoEx, "xsi:type",
    2692             :                                        "typens:GPFieldInfoEx");
    2693        1137 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Name",
    2694        1137 :                                         poGDBFieldDefn->GetName().c_str());
    2695        1137 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "FieldType",
    2696             :                                         "esriFieldTypeOID");
    2697        1137 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "IsNullable", "false");
    2698        1137 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Length", "4");
    2699        1137 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Precision", "0");
    2700        1137 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Scale", "0");
    2701        1137 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Required", "true");
    2702             :         }
    2703        4998 :         else if (poGDBFieldDefn->GetType() == FGFT_GEOMETRY)
    2704             :         {
    2705             :             auto GPFieldInfoEx =
    2706         922 :                 CPLCreateXMLNode(GPFieldInfoExs, CXT_Element, "GPFieldInfoEx");
    2707         922 :             CPLAddXMLAttributeAndValue(GPFieldInfoEx, "xsi:type",
    2708             :                                        "typens:GPFieldInfoEx");
    2709         922 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Name",
    2710         922 :                                         poGDBFieldDefn->GetName().c_str());
    2711         922 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "FieldType",
    2712             :                                         "esriFieldTypeGeometry");
    2713         922 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "IsNullable",
    2714         922 :                                         poGDBFieldDefn->IsNullable() ? "true"
    2715             :                                                                      : "false");
    2716         922 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Length", "0");
    2717         922 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Precision", "0");
    2718         922 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Scale", "0");
    2719         922 :             CPLCreateXMLElementAndValue(GPFieldInfoEx, "Required", "true");
    2720             :         }
    2721             :         else
    2722             :         {
    2723        4076 :             const int nOGRIdx = m_poFeatureDefn->GetFieldIndex(
    2724        4076 :                 poGDBFieldDefn->GetName().c_str());
    2725        4076 :             if (nOGRIdx >= 0)
    2726             :             {
    2727        4076 :                 const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(nOGRIdx);
    2728        4076 :                 CPLAddXMLChild(GPFieldInfoExs, CreateXMLFieldDefinition(
    2729             :                                                    poFieldDefn, poGDBFieldDefn,
    2730        4076 :                                                    m_bArcGISPro32OrLater));
    2731             :             }
    2732             :         }
    2733             :     }
    2734             : 
    2735        1137 :     CPLCreateXMLElementAndValue(psRoot, "CLSID",
    2736        1137 :                                 m_eGeomType == wkbNone
    2737             :                                     ? "{7A566981-C114-11D2-8A28-006097AFF44E}"
    2738             :                                     : "{52353152-891A-11D0-BEC6-00805F7C4268}");
    2739        1137 :     CPLCreateXMLElementAndValue(psRoot, "EXTCLSID", "");
    2740             : 
    2741             :     const char *pszLayerAlias =
    2742        1137 :         m_aosCreationOptions.FetchNameValue("LAYER_ALIAS");
    2743        1137 :     if (pszLayerAlias != nullptr)
    2744             :     {
    2745           1 :         CPLCreateXMLElementAndValue(psRoot, "AliasName", pszLayerAlias);
    2746             :     }
    2747             : 
    2748        1137 :     CPLCreateXMLElementAndValue(psRoot, "IsTimeInUTC",
    2749        1137 :                                 m_bTimeInUTC ? "true" : " false");
    2750             : 
    2751        1137 :     if (m_eGeomType != wkbNone)
    2752             :     {
    2753         922 :         const auto poGeomFieldDefn = m_poLyrTable->GetGeomField();
    2754         922 :         CPLCreateXMLElementAndValue(psRoot, "FeatureType", "esriFTSimple");
    2755             : 
    2756         922 :         const char *pszShapeType = "";
    2757         922 :         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         281 :             case FGTGT_POLYGON:
    2771         281 :                 pszShapeType = "esriGeometryPolygon";
    2772         281 :                 break;
    2773          66 :             case FGTGT_MULTIPATCH:
    2774          66 :                 pszShapeType = "esriGeometryMultiPatch";
    2775          66 :                 break;
    2776             :         }
    2777         922 :         CPLCreateXMLElementAndValue(psRoot, "ShapeType", pszShapeType);
    2778         922 :         CPLCreateXMLElementAndValue(psRoot, "ShapeFieldName",
    2779         922 :                                     poGeomFieldDefn->GetName().c_str());
    2780             : 
    2781         922 :         const bool bGeomTypeHasZ = CPL_TO_BOOL(OGR_GT_HasZ(m_eGeomType));
    2782         922 :         const bool bGeomTypeHasM = CPL_TO_BOOL(OGR_GT_HasM(m_eGeomType));
    2783         922 :         CPLCreateXMLElementAndValue(psRoot, "HasM",
    2784             :                                     bGeomTypeHasM ? "true" : "false");
    2785         922 :         CPLCreateXMLElementAndValue(psRoot, "HasZ",
    2786             :                                     bGeomTypeHasZ ? "true" : "false");
    2787         922 :         CPLCreateXMLElementAndValue(psRoot, "HasSpatialIndex", "false");
    2788             :         const char *pszAreaFieldName =
    2789         922 :             m_iAreaField >= 0
    2790         922 :                 ? m_poFeatureDefn->GetFieldDefn(m_iAreaField)->GetNameRef()
    2791         922 :                 : "";
    2792         922 :         CPLCreateXMLElementAndValue(psRoot, "AreaFieldName", pszAreaFieldName);
    2793             :         const char *pszLengthFieldName =
    2794         922 :             m_iLengthField >= 0
    2795         922 :                 ? m_poFeatureDefn->GetFieldDefn(m_iLengthField)->GetNameRef()
    2796         922 :                 : "";
    2797         922 :         CPLCreateXMLElementAndValue(psRoot, "LengthFieldName",
    2798             :                                     pszLengthFieldName);
    2799             : 
    2800         922 :         XMLSerializeGeomFieldBase(psRoot, poGeomFieldDefn, GetSpatialRef());
    2801             :     }
    2802             : 
    2803        1137 :     char *pszDefinition = CPLSerializeXMLTree(oTree.get());
    2804        1137 :     m_osDefinition = pszDefinition;
    2805        1137 :     CPLFree(pszDefinition);
    2806             : }
    2807             : 
    2808             : /************************************************************************/
    2809             : /*                            RegisterTable()                           */
    2810             : /************************************************************************/
    2811             : 
    2812         326 : bool OGROpenFileGDBLayer::RegisterTable()
    2813             : {
    2814         326 :     m_bRegisteredTable = true;
    2815             : 
    2816         326 :     CPLAssert(!m_osThisGUID.empty());
    2817             : 
    2818             :     const char *pszFeatureDataset =
    2819         326 :         m_aosCreationOptions.FetchNameValue("FEATURE_DATASET");
    2820         326 :     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         638 :         if (!m_poDS->RegisterInItemRelationships(m_poDS->m_osRootGUID,
    2832         319 :                                                  m_osThisGUID,
    2833             :                                                  // DatasetInFolder
    2834             :                                                  pszDatasetInFolderUUID))
    2835             :         {
    2836           0 :             return false;
    2837             :         }
    2838             :     }
    2839             : 
    2840         326 :     if (m_eGeomType != wkbNone)
    2841             :     {
    2842         239 :         return m_poDS->RegisterFeatureClassInItems(
    2843         239 :             m_osThisGUID, m_osName, m_osPath, m_poLyrTable,
    2844         239 :             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       11929 : OGRErr OGROpenFileGDBLayer::SyncToDisk()
    2859             : {
    2860       11929 :     if (!m_bEditable || m_poLyrTable == nullptr)
    2861       10937 :         return OGRERR_NONE;
    2862             : 
    2863         992 :     if (!m_bRegisteredTable && !RegisterTable())
    2864           0 :         return OGRERR_FAILURE;
    2865             : 
    2866         992 :     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           6 : bool OGROpenFileGDBLayer::Repack(GDALProgressFunc pfnProgress,
    2913             :                                  void *pProgressData)
    2914             : {
    2915           6 :     if (!m_bEditable)
    2916           0 :         return false;
    2917             : 
    2918           6 :     if (!BuildLayerDefinition())
    2919           0 :         return false;
    2920             : 
    2921           6 :     return m_poLyrTable->Repack(pfnProgress, pProgressData);
    2922             : }
    2923             : 
    2924             : /************************************************************************/
    2925             : /*                        RecomputeExtent()                             */
    2926             : /************************************************************************/
    2927             : 
    2928           2 : void OGROpenFileGDBLayer::RecomputeExtent()
    2929             : {
    2930           2 :     if (!m_bEditable)
    2931           0 :         return;
    2932             : 
    2933           2 :     if (!BuildLayerDefinition())
    2934           0 :         return;
    2935             : 
    2936           2 :     m_poLyrTable->RecomputeExtent();
    2937             : }
    2938             : 
    2939             : /************************************************************************/
    2940             : /*                        CheckFreeListConsistency()                    */
    2941             : /************************************************************************/
    2942             : 
    2943           8 : bool OGROpenFileGDBLayer::CheckFreeListConsistency()
    2944             : {
    2945           8 :     if (!BuildLayerDefinition())
    2946           0 :         return false;
    2947             : 
    2948           8 :     return m_poLyrTable->CheckFreeListConsistency();
    2949             : }
    2950             : 
    2951             : /************************************************************************/
    2952             : /*                        BeginEmulatedTransaction()                    */
    2953             : /************************************************************************/
    2954             : 
    2955           8 : bool OGROpenFileGDBLayer::BeginEmulatedTransaction()
    2956             : {
    2957           8 :     if (!BuildLayerDefinition())
    2958           0 :         return false;
    2959             : 
    2960           8 :     if (SyncToDisk() != OGRERR_NONE)
    2961           0 :         return false;
    2962             : 
    2963           8 :     bool bRet = true;
    2964             : 
    2965          16 :     const std::string osThisDirname = CPLGetPathSafe(m_osGDBFilename.c_str());
    2966             :     const std::string osThisBasename =
    2967           8 :         CPLGetBasenameSafe(m_osGDBFilename.c_str());
    2968           8 :     char **papszFiles = VSIReadDir(osThisDirname.c_str());
    2969         193 :     for (char **papszIter = papszFiles;
    2970         193 :          papszIter != nullptr && *papszIter != nullptr; ++papszIter)
    2971             :     {
    2972         370 :         const std::string osBasename = CPLGetBasenameSafe(*papszIter);
    2973         185 :         if (osBasename == osThisBasename)
    2974             :         {
    2975             :             const std::string osDestFilename = CPLFormFilenameSafe(
    2976          48 :                 m_poDS->GetBackupDirName().c_str(), *papszIter, nullptr);
    2977             :             const std::string osSourceFilename =
    2978          48 :                 CPLFormFilenameSafe(osThisDirname.c_str(), *papszIter, nullptr);
    2979          24 :             if (CPLCopyFile(osDestFilename.c_str(), osSourceFilename.c_str()) !=
    2980             :                 0)
    2981             :             {
    2982           0 :                 bRet = false;
    2983             :             }
    2984             :         }
    2985             :     }
    2986           8 :     CSLDestroy(papszFiles);
    2987             : 
    2988           8 :     m_bHasCreatedBackupForTransaction = true;
    2989             : 
    2990           8 :     m_poFeatureDefnBackup.reset(m_poFeatureDefn->Clone());
    2991             : 
    2992           8 :     return bRet;
    2993             : }
    2994             : 
    2995             : /************************************************************************/
    2996             : /*                        CommitEmulatedTransaction()                   */
    2997             : /************************************************************************/
    2998             : 
    2999           3 : bool OGROpenFileGDBLayer::CommitEmulatedTransaction()
    3000             : {
    3001           3 :     m_poFeatureDefnBackup.reset();
    3002             : 
    3003           3 :     m_bHasCreatedBackupForTransaction = false;
    3004           3 :     return true;
    3005             : }
    3006             : 
    3007             : /************************************************************************/
    3008             : /*                        RollbackEmulatedTransaction()                 */
    3009             : /************************************************************************/
    3010             : 
    3011           6 : bool OGROpenFileGDBLayer::RollbackEmulatedTransaction()
    3012             : {
    3013           6 :     if (!m_bHasCreatedBackupForTransaction)
    3014           2 :         return true;
    3015             : 
    3016           4 :     SyncToDisk();
    3017             : 
    3018             :     // Restore feature definition
    3019           8 :     if (m_poFeatureDefnBackup != nullptr &&
    3020           4 :         !m_poFeatureDefn->IsSame(m_poFeatureDefnBackup.get()))
    3021             :     {
    3022           2 :         auto oTemporaryUnsealer(m_poFeatureDefn->GetTemporaryUnsealer());
    3023             :         {
    3024           1 :             const int nFieldCount = m_poFeatureDefn->GetFieldCount();
    3025           2 :             for (int i = nFieldCount - 1; i >= 0; i--)
    3026           1 :                 m_poFeatureDefn->DeleteFieldDefn(i);
    3027             :         }
    3028             :         {
    3029           1 :             const int nFieldCount = m_poFeatureDefnBackup->GetFieldCount();
    3030           3 :             for (int i = 0; i < nFieldCount; i++)
    3031           4 :                 m_poFeatureDefn->AddFieldDefn(
    3032           2 :                     m_poFeatureDefnBackup->GetFieldDefn(i));
    3033             :         }
    3034             :     }
    3035           4 :     m_poFeatureDefnBackup.reset();
    3036             : 
    3037           4 :     Close();
    3038             : 
    3039           4 :     bool bRet = true;
    3040             : 
    3041           8 :     const std::string osThisDirname = CPLGetPathSafe(m_osGDBFilename.c_str());
    3042             :     const std::string osThisBasename =
    3043           4 :         CPLGetBasenameSafe(m_osGDBFilename.c_str());
    3044             : 
    3045             :     // Delete files in working directory that match our basename
    3046             :     {
    3047           4 :         char **papszFiles = VSIReadDir(osThisDirname.c_str());
    3048          93 :         for (char **papszIter = papszFiles;
    3049          93 :              papszIter != nullptr && *papszIter != nullptr; ++papszIter)
    3050             :         {
    3051         178 :             const std::string osBasename = CPLGetBasenameSafe(*papszIter);
    3052          89 :             if (osBasename == osThisBasename)
    3053             :             {
    3054             :                 const std::string osDestFilename = CPLFormFilenameSafe(
    3055          18 :                     osThisDirname.c_str(), *papszIter, nullptr);
    3056           9 :                 VSIUnlink(osDestFilename.c_str());
    3057             :             }
    3058             :         }
    3059           4 :         CSLDestroy(papszFiles);
    3060             :     }
    3061             : 
    3062             :     // Restore backup files
    3063           4 :     bool bBackupFound = false;
    3064             :     {
    3065           4 :         char **papszFiles = VSIReadDir(m_poDS->GetBackupDirName().c_str());
    3066          66 :         for (char **papszIter = papszFiles;
    3067          66 :              papszIter != nullptr && *papszIter != nullptr; ++papszIter)
    3068             :         {
    3069         124 :             const std::string osBasename = CPLGetBasenameSafe(*papszIter);
    3070          62 :             if (osBasename == osThisBasename)
    3071             :             {
    3072          12 :                 bBackupFound = true;
    3073             :                 const std::string osDestFilename = CPLFormFilenameSafe(
    3074          24 :                     osThisDirname.c_str(), *papszIter, nullptr);
    3075             :                 const std::string osSourceFilename = CPLFormFilenameSafe(
    3076          24 :                     m_poDS->GetBackupDirName().c_str(), *papszIter, nullptr);
    3077          12 :                 if (CPLCopyFile(osDestFilename.c_str(),
    3078          12 :                                 osSourceFilename.c_str()) != 0)
    3079             :                 {
    3080           0 :                     bRet = false;
    3081             :                 }
    3082             :             }
    3083             :         }
    3084           4 :         CSLDestroy(papszFiles);
    3085             :     }
    3086             : 
    3087           4 :     if (bBackupFound)
    3088             :     {
    3089           4 :         m_poLyrTable = new FileGDBTable();
    3090           4 :         if (m_poLyrTable->Open(m_osGDBFilename, m_bEditable, GetDescription()))
    3091             :         {
    3092           4 :             if (m_iGeomFieldIdx >= 0)
    3093             :             {
    3094           1 :                 m_iGeomFieldIdx = m_poLyrTable->GetGeomFieldIdx();
    3095           1 :                 if (m_iGeomFieldIdx < 0)
    3096             :                 {
    3097           0 :                     Close();
    3098           0 :                     bRet = false;
    3099             :                 }
    3100             :                 else
    3101             :                 {
    3102           1 :                     m_bValidLayerDefn = TRUE;
    3103             :                 }
    3104             :             }
    3105             :             else
    3106             :             {
    3107           3 :                 m_bValidLayerDefn = TRUE;
    3108             :             }
    3109             :         }
    3110             :         else
    3111             :         {
    3112           0 :             Close();
    3113           0 :             bRet = false;
    3114             :         }
    3115             :     }
    3116             : 
    3117           4 :     m_bHasCreatedBackupForTransaction = false;
    3118             : 
    3119           4 :     delete m_poAttributeIterator;
    3120           4 :     m_poAttributeIterator = nullptr;
    3121             : 
    3122           4 :     delete m_poIterMinMax;
    3123           4 :     m_poIterMinMax = nullptr;
    3124             : 
    3125           4 :     delete m_poSpatialIndexIterator;
    3126           4 :     m_poSpatialIndexIterator = nullptr;
    3127             : 
    3128           4 :     delete m_poCombinedIterator;
    3129           4 :     m_poCombinedIterator = nullptr;
    3130             : 
    3131           4 :     if (m_pQuadTree != nullptr)
    3132           0 :         CPLQuadTreeDestroy(m_pQuadTree);
    3133           4 :     m_pQuadTree = nullptr;
    3134             : 
    3135           4 :     CPLFree(m_pahFilteredFeatures);
    3136           4 :     m_pahFilteredFeatures = nullptr;
    3137             : 
    3138           4 :     m_nFilteredFeatureCount = -1;
    3139             : 
    3140           4 :     m_eSpatialIndexState = SPI_INVALID;
    3141             : 
    3142           4 :     if (m_poLyrTable && m_iGeomFieldIdx >= 0)
    3143             :     {
    3144           1 :         m_poGeomConverter.reset(FileGDBOGRGeometryConverter::BuildConverter(
    3145           1 :             m_poLyrTable->GetGeomField()));
    3146             :     }
    3147             : 
    3148           4 :     return bRet;
    3149             : }
    3150             : 
    3151             : /************************************************************************/
    3152             : /*                           Rename()                                   */
    3153             : /************************************************************************/
    3154             : 
    3155          10 : OGRErr OGROpenFileGDBLayer::Rename(const char *pszDstTableName)
    3156             : {
    3157          10 :     if (!m_bEditable)
    3158           0 :         return OGRERR_FAILURE;
    3159             : 
    3160          10 :     if (!BuildLayerDefinition())
    3161           0 :         return OGRERR_FAILURE;
    3162             : 
    3163          10 :     if (SyncToDisk() != OGRERR_NONE)
    3164           0 :         return OGRERR_FAILURE;
    3165             : 
    3166          10 :     if (m_poDS->IsInTransaction() &&
    3167           0 :         ((!m_bHasCreatedBackupForTransaction && !BeginEmulatedTransaction()) ||
    3168           0 :          !m_poDS->BackupSystemTablesForTransaction()))
    3169             :     {
    3170           0 :         return OGRERR_FAILURE;
    3171             :     }
    3172             : 
    3173          30 :     const std::string osLaunderedName(GetLaunderedLayerName(pszDstTableName));
    3174          10 :     if (pszDstTableName != osLaunderedName)
    3175             :     {
    3176           6 :         CPLError(CE_Failure, CPLE_AppDefined,
    3177             :                  "%s is not a valid layer name. %s would be a valid one.",
    3178             :                  pszDstTableName, osLaunderedName.c_str());
    3179           6 :         return OGRERR_FAILURE;
    3180             :     }
    3181             : 
    3182           4 :     if (m_poDS->GetLayerByName(pszDstTableName) != nullptr)
    3183             :     {
    3184           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Layer %s already exists",
    3185             :                  pszDstTableName);
    3186           0 :         return OGRERR_FAILURE;
    3187             :     }
    3188             : 
    3189           8 :     const std::string osOldName(m_osName);
    3190             : 
    3191           4 :     m_osName = pszDstTableName;
    3192           4 :     SetDescription(pszDstTableName);
    3193           4 :     whileUnsealing(m_poFeatureDefn)->SetName(pszDstTableName);
    3194             : 
    3195           4 :     auto nLastSlashPos = m_osPath.rfind('\\');
    3196           4 :     if (nLastSlashPos != std::string::npos)
    3197             :     {
    3198           4 :         m_osPath.resize(nLastSlashPos + 1);
    3199             :     }
    3200             :     else
    3201             :     {
    3202           0 :         m_osPath = '\\';
    3203             :     }
    3204           4 :     m_osPath += m_osName;
    3205             : 
    3206           4 :     RefreshXMLDefinitionInMemory();
    3207             : 
    3208             :     // Update GDB_SystemCatalog
    3209             :     {
    3210           4 :         FileGDBTable oTable;
    3211           4 :         if (!oTable.Open(m_poDS->m_osGDBSystemCatalogFilename.c_str(), true))
    3212           0 :             return OGRERR_FAILURE;
    3213             : 
    3214           4 :         FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, OGRERR_FAILURE);
    3215             : 
    3216          40 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    3217             :              ++iCurFeat)
    3218             :         {
    3219          40 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    3220          40 :             if (iCurFeat < 0)
    3221           0 :                 break;
    3222          40 :             const auto psName = oTable.GetFieldValue(iName);
    3223          40 :             if (psName && psName->String == osOldName)
    3224             :             {
    3225           4 :                 auto asFields = oTable.GetAllFieldValues();
    3226             : 
    3227           4 :                 CPLFree(asFields[iName].String);
    3228           4 :                 asFields[iName].String = CPLStrdup(m_osName.c_str());
    3229             : 
    3230             :                 bool bRet =
    3231           8 :                     oTable.UpdateFeature(iCurFeat + 1, asFields, nullptr) &&
    3232           4 :                     oTable.Sync();
    3233           4 :                 oTable.FreeAllFieldValues(asFields);
    3234           4 :                 if (!bRet)
    3235           0 :                     return OGRERR_FAILURE;
    3236           4 :                 break;
    3237             :             }
    3238             :         }
    3239             :     }
    3240             : 
    3241             :     // Update GDB_Items
    3242             :     {
    3243           4 :         FileGDBTable oTable;
    3244           4 :         if (!oTable.Open(m_poDS->m_osGDBItemsFilename.c_str(), true))
    3245           0 :             return OGRERR_FAILURE;
    3246             : 
    3247           4 :         FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, OGRERR_FAILURE);
    3248           4 :         FETCH_FIELD_IDX_WITH_RET(iPath, "Path", FGFT_STRING, OGRERR_FAILURE);
    3249           4 :         FETCH_FIELD_IDX_WITH_RET(iPhysicalName, "PhysicalName", FGFT_STRING,
    3250             :                                  OGRERR_FAILURE);
    3251           4 :         FETCH_FIELD_IDX_WITH_RET(iDefinition, "Definition", FGFT_XML,
    3252             :                                  OGRERR_FAILURE);
    3253             : 
    3254          18 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    3255             :              ++iCurFeat)
    3256             :         {
    3257          18 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    3258          18 :             if (iCurFeat < 0)
    3259           0 :                 break;
    3260          18 :             const auto psName = oTable.GetFieldValue(iName);
    3261          18 :             if (psName && psName->String == osOldName)
    3262             :             {
    3263           4 :                 auto asFields = oTable.GetAllFieldValues();
    3264             : 
    3265           4 :                 CPLFree(asFields[iName].String);
    3266           4 :                 asFields[iName].String = CPLStrdup(m_osName.c_str());
    3267             : 
    3268           8 :                 if (!OGR_RawField_IsNull(&asFields[iPath]) &&
    3269           4 :                     !OGR_RawField_IsUnset(&asFields[iPath]))
    3270             :                 {
    3271           4 :                     CPLFree(asFields[iPath].String);
    3272             :                 }
    3273           4 :                 asFields[iPath].String = CPLStrdup(m_osPath.c_str());
    3274             : 
    3275           8 :                 if (!OGR_RawField_IsNull(&asFields[iPhysicalName]) &&
    3276           4 :                     !OGR_RawField_IsUnset(&asFields[iPhysicalName]))
    3277             :                 {
    3278           4 :                     CPLFree(asFields[iPhysicalName].String);
    3279             :                 }
    3280           4 :                 CPLString osUCName(m_osName);
    3281           4 :                 osUCName.toupper();
    3282           4 :                 asFields[iPhysicalName].String = CPLStrdup(osUCName.c_str());
    3283             : 
    3284           8 :                 if (!OGR_RawField_IsNull(&asFields[iDefinition]) &&
    3285           4 :                     !OGR_RawField_IsUnset(&asFields[iDefinition]))
    3286             :                 {
    3287           4 :                     CPLFree(asFields[iDefinition].String);
    3288             :                 }
    3289           8 :                 asFields[iDefinition].String =
    3290           4 :                     CPLStrdup(m_osDefinition.c_str());
    3291             : 
    3292             :                 bool bRet =
    3293           8 :                     oTable.UpdateFeature(iCurFeat + 1, asFields, nullptr) &&
    3294           4 :                     oTable.Sync();
    3295           4 :                 oTable.FreeAllFieldValues(asFields);
    3296           4 :                 if (!bRet)
    3297           0 :                     return OGRERR_FAILURE;
    3298           4 :                 break;
    3299             :             }
    3300             :         }
    3301             :     }
    3302             : 
    3303           4 :     return OGRERR_NONE;
    3304             : }

Generated by: LCOV version 1.14