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

Generated by: LCOV version 1.14