LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/openfilegdb - ogropenfilegdbdatasource_write.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1051 1336 78.7 %
Date: 2025-01-18 12:42:00 Functions: 37 37 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Implements Open FileGDB OGR driver.
       5             :  * Author:   Even Rouault, <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2022, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_port.h"
      14             : #include "ogr_openfilegdb.h"
      15             : 
      16             : #include <stddef.h>
      17             : #include <stdio.h>
      18             : #include <string.h>
      19             : #include <limits>
      20             : #include <map>
      21             : #include <memory>
      22             : #include <string>
      23             : #include <utility>
      24             : #include <vector>
      25             : 
      26             : #include "cpl_conv.h"
      27             : #include "cpl_error.h"
      28             : #include "cpl_string.h"
      29             : #include "cpl_vsi.h"
      30             : #include "filegdbtable.h"
      31             : #include "gdal.h"
      32             : #include "ogr_core.h"
      33             : #include "ogrsf_frmts.h"
      34             : 
      35             : #include "filegdb_fielddomain.h"
      36             : #include "filegdb_relationship.h"
      37             : 
      38             : /***********************************************************************/
      39             : /*                    GetExistingSpatialRef()                          */
      40             : /***********************************************************************/
      41             : 
      42         195 : bool OGROpenFileGDBDataSource::GetExistingSpatialRef(
      43             :     const std::string &osWKT, double dfXOrigin, double dfYOrigin,
      44             :     double dfXYScale, double dfZOrigin, double dfZScale, double dfMOrigin,
      45             :     double dfMScale, double dfXYTolerance, double dfZTolerance,
      46             :     double dfMTolerance)
      47             : {
      48         390 :     FileGDBTable oTable;
      49         195 :     if (!oTable.Open(m_osGDBSpatialRefsFilename.c_str(), false))
      50           0 :         return false;
      51             : 
      52         195 :     FETCH_FIELD_IDX(iSRTEXT, "SRTEXT", FGFT_STRING);
      53         195 :     FETCH_FIELD_IDX(iFalseX, "FalseX", FGFT_FLOAT64);
      54         195 :     FETCH_FIELD_IDX(iFalseY, "FalseY", FGFT_FLOAT64);
      55         195 :     FETCH_FIELD_IDX(iXYUnits, "XYUnits", FGFT_FLOAT64);
      56         195 :     FETCH_FIELD_IDX(iFalseZ, "FalseZ", FGFT_FLOAT64);
      57         195 :     FETCH_FIELD_IDX(iZUnits, "ZUnits", FGFT_FLOAT64);
      58         195 :     FETCH_FIELD_IDX(iFalseM, "FalseM", FGFT_FLOAT64);
      59         195 :     FETCH_FIELD_IDX(iMUnits, "MUnits", FGFT_FLOAT64);
      60         195 :     FETCH_FIELD_IDX(iXYTolerance, "XYTolerance", FGFT_FLOAT64);
      61         195 :     FETCH_FIELD_IDX(iZTolerance, "ZTolerance", FGFT_FLOAT64);
      62         195 :     FETCH_FIELD_IDX(iMTolerance, "MTolerance", FGFT_FLOAT64);
      63             : 
      64         195 :     int64_t iCurFeat = 0;
      65         391 :     while (iCurFeat < oTable.GetTotalRecordCount())
      66             :     {
      67         220 :         iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
      68         220 :         if (iCurFeat < 0)
      69           0 :             break;
      70         220 :         iCurFeat++;
      71         220 :         const auto psSRTEXT = oTable.GetFieldValue(iSRTEXT);
      72         220 :         if (psSRTEXT && psSRTEXT->String == osWKT)
      73             :         {
      74         250 :             const auto fetchRealVal = [&oTable](int idx, double dfExpected)
      75             :             {
      76         250 :                 const auto psVal = oTable.GetFieldValue(idx);
      77         250 :                 return psVal && psVal->Real == dfExpected;
      78          34 :             };
      79          34 :             if (fetchRealVal(iFalseX, dfXOrigin) &&
      80          24 :                 fetchRealVal(iFalseY, dfYOrigin) &&
      81          24 :                 fetchRealVal(iXYUnits, dfXYScale) &&
      82          24 :                 fetchRealVal(iFalseZ, dfZOrigin) &&
      83          24 :                 fetchRealVal(iZUnits, dfZScale) &&
      84          24 :                 fetchRealVal(iFalseM, dfMOrigin) &&
      85          24 :                 fetchRealVal(iMUnits, dfMScale) &&
      86          24 :                 fetchRealVal(iXYTolerance, dfXYTolerance) &&
      87          82 :                 fetchRealVal(iZTolerance, dfZTolerance) &&
      88          24 :                 fetchRealVal(iMTolerance, dfMTolerance))
      89             :             {
      90          24 :                 return true;
      91             :             }
      92             :         }
      93             :     }
      94         171 :     return false;
      95             : }
      96             : 
      97             : /***********************************************************************/
      98             : /*                       AddNewSpatialRef()                            */
      99             : /***********************************************************************/
     100             : 
     101         400 : bool OGROpenFileGDBDataSource::AddNewSpatialRef(
     102             :     const std::string &osWKT, double dfXOrigin, double dfYOrigin,
     103             :     double dfXYScale, double dfZOrigin, double dfZScale, double dfMOrigin,
     104             :     double dfMScale, double dfXYTolerance, double dfZTolerance,
     105             :     double dfMTolerance)
     106             : {
     107         800 :     FileGDBTable oTable;
     108         400 :     if (!oTable.Open(m_osGDBSpatialRefsFilename.c_str(), true))
     109           0 :         return false;
     110             : 
     111         400 :     FETCH_FIELD_IDX(iSRTEXT, "SRTEXT", FGFT_STRING);
     112         400 :     FETCH_FIELD_IDX(iFalseX, "FalseX", FGFT_FLOAT64);
     113         400 :     FETCH_FIELD_IDX(iFalseY, "FalseY", FGFT_FLOAT64);
     114         400 :     FETCH_FIELD_IDX(iXYUnits, "XYUnits", FGFT_FLOAT64);
     115         400 :     FETCH_FIELD_IDX(iFalseZ, "FalseZ", FGFT_FLOAT64);
     116         400 :     FETCH_FIELD_IDX(iZUnits, "ZUnits", FGFT_FLOAT64);
     117         400 :     FETCH_FIELD_IDX(iFalseM, "FalseM", FGFT_FLOAT64);
     118         400 :     FETCH_FIELD_IDX(iMUnits, "MUnits", FGFT_FLOAT64);
     119         400 :     FETCH_FIELD_IDX(iXYTolerance, "XYTolerance", FGFT_FLOAT64);
     120         400 :     FETCH_FIELD_IDX(iZTolerance, "ZTolerance", FGFT_FLOAT64);
     121         400 :     FETCH_FIELD_IDX(iMTolerance, "MTolerance", FGFT_FLOAT64);
     122             : 
     123         400 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     124         800 :                                  FileGDBField::UNSET_FIELD);
     125         400 :     fields[iSRTEXT].String = const_cast<char *>(osWKT.c_str());
     126         400 :     fields[iFalseX].Real = dfXOrigin;
     127         400 :     fields[iFalseY].Real = dfYOrigin;
     128         400 :     fields[iXYUnits].Real = dfXYScale;
     129         400 :     fields[iFalseZ].Real = dfZOrigin;
     130         400 :     fields[iZUnits].Real = dfZScale;
     131         400 :     fields[iFalseM].Real = dfMOrigin;
     132         400 :     fields[iMUnits].Real = dfMScale;
     133         400 :     fields[iXYTolerance].Real = dfXYTolerance;
     134         400 :     fields[iZTolerance].Real = dfZTolerance;
     135         400 :     fields[iMTolerance].Real = dfMTolerance;
     136             : 
     137         400 :     return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
     138             : }
     139             : 
     140             : /***********************************************************************/
     141             : /*                    RegisterLayerInSystemCatalog()                   */
     142             : /***********************************************************************/
     143             : 
     144         280 : bool OGROpenFileGDBDataSource::RegisterLayerInSystemCatalog(
     145             :     const std::string &osLayerName)
     146             : {
     147         560 :     FileGDBTable oTable;
     148         280 :     if (!oTable.Open(m_osGDBSystemCatalogFilename.c_str(), true))
     149           0 :         return false;
     150             : 
     151         280 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
     152         280 :     FETCH_FIELD_IDX(iFileFormat, "FileFormat", FGFT_INT32);
     153             : 
     154         280 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     155         560 :                                  FileGDBField::UNSET_FIELD);
     156         280 :     fields[iName].String = const_cast<char *>(osLayerName.c_str());
     157         280 :     fields[iFileFormat].Integer = 0;
     158         280 :     return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
     159             : }
     160             : 
     161             : /***********************************************************************/
     162             : /*                    RegisterInItemRelationships()                    */
     163             : /***********************************************************************/
     164             : 
     165         337 : bool OGROpenFileGDBDataSource::RegisterInItemRelationships(
     166             :     const std::string &osOriginGUID, const std::string &osDestGUID,
     167             :     const std::string &osTypeGUID)
     168             : {
     169         674 :     FileGDBTable oTable;
     170         337 :     if (!oTable.Open(m_osGDBItemRelationshipsFilename.c_str(), true))
     171           0 :         return false;
     172             : 
     173         337 :     FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
     174         337 :     FETCH_FIELD_IDX(iOriginID, "OriginID", FGFT_GUID);
     175         337 :     FETCH_FIELD_IDX(iDestID, "DestID", FGFT_GUID);
     176         337 :     FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
     177         337 :     FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
     178             : 
     179         337 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     180        1011 :                                  FileGDBField::UNSET_FIELD);
     181         337 :     const std::string osGUID = OFGDBGenerateUUID();
     182         337 :     fields[iUUID].String = const_cast<char *>(osGUID.c_str());
     183         337 :     fields[iOriginID].String = const_cast<char *>(osOriginGUID.c_str());
     184         337 :     fields[iDestID].String = const_cast<char *>(osDestGUID.c_str());
     185         337 :     fields[iType].String = const_cast<char *>(osTypeGUID.c_str());
     186         337 :     fields[iProperties].Integer = 1;
     187         337 :     return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
     188             : }
     189             : 
     190             : /***********************************************************************/
     191             : /*              RegisterRelationshipInItemRelationships()              */
     192             : /***********************************************************************/
     193             : 
     194          16 : bool OGROpenFileGDBDataSource::RegisterRelationshipInItemRelationships(
     195             :     const std::string &osRelationshipGUID, const std::string &osOriginGUID,
     196             :     const std::string &osDestGUID)
     197             : {
     198             :     // Relationships to register:
     199             :     // 1. Origin table -> new relationship as DatasetsRelatedThrough
     200             :     // 2. Destination table -> new relationship as DatasetsRelatedThrough
     201             :     // 3. Root dataset -> new relationship as DatasetInFolder
     202          16 :     if (!RegisterInItemRelationships(osOriginGUID, osRelationshipGUID,
     203             :                                      pszDatasetsRelatedThroughUUID))
     204           0 :         return false;
     205          16 :     if (!RegisterInItemRelationships(osDestGUID, osRelationshipGUID,
     206             :                                      pszDatasetsRelatedThroughUUID))
     207           0 :         return false;
     208          16 :     if (!RegisterInItemRelationships(m_osRootGUID, osRelationshipGUID,
     209             :                                      pszDatasetInFolderUUID))
     210           0 :         return false;
     211             : 
     212          16 :     return true;
     213             : }
     214             : 
     215             : /***********************************************************************/
     216             : /*              RemoveRelationshipFromItemRelationships()              */
     217             : /***********************************************************************/
     218             : 
     219           3 : bool OGROpenFileGDBDataSource::RemoveRelationshipFromItemRelationships(
     220             :     const std::string &osRelationshipGUID)
     221             : {
     222           6 :     FileGDBTable oTable;
     223           3 :     if (!oTable.Open(m_osGDBItemRelationshipsFilename.c_str(), true))
     224           0 :         return false;
     225             : 
     226             :     // while we've only found item relationships with the relationship UUID in
     227             :     // the DestID field, let's be super-careful and also check against the
     228             :     // OriginID UUID, just in case there's some previously unencountered
     229             :     // situations where a relationship UUID is placed in OriginID
     230           3 :     FETCH_FIELD_IDX_WITH_RET(iOriginID, "OriginID", FGFT_GUID, false);
     231           3 :     FETCH_FIELD_IDX_WITH_RET(iDestID, "DestID", FGFT_GUID, false);
     232             : 
     233          68 :     for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
     234             :          ++iCurFeat)
     235             :     {
     236          65 :         iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
     237          65 :         if (iCurFeat < 0)
     238           0 :             break;
     239             : 
     240          65 :         const auto psOriginID = oTable.GetFieldValue(iOriginID);
     241          65 :         if (psOriginID && psOriginID->String == osRelationshipGUID)
     242             :         {
     243           0 :             oTable.DeleteFeature(iCurFeat + 1);
     244             :         }
     245             :         else
     246             :         {
     247          65 :             const auto psDestID = oTable.GetFieldValue(iDestID);
     248          65 :             if (psDestID && psDestID->String == osRelationshipGUID)
     249             :             {
     250           9 :                 oTable.DeleteFeature(iCurFeat + 1);
     251             :             }
     252             :         }
     253             :     }
     254             : 
     255           3 :     return true;
     256             : }
     257             : 
     258             : /***********************************************************************/
     259             : /*                      LinkDomainToTable()                            */
     260             : /***********************************************************************/
     261             : 
     262           6 : bool OGROpenFileGDBDataSource::LinkDomainToTable(
     263             :     const std::string &osDomainName, const std::string &osLayerGUID)
     264             : {
     265          12 :     std::string osDomainUUID;
     266           6 :     if (!FindUUIDFromName(osDomainName, osDomainUUID))
     267           0 :         return false;
     268             : 
     269             :     // Check if the domain is already linked to this table
     270             :     {
     271           6 :         FileGDBTable oTable;
     272           6 :         if (!oTable.Open(m_osGDBItemRelationshipsFilename.c_str(), false))
     273           0 :             return false;
     274             : 
     275           6 :         FETCH_FIELD_IDX(iOriginID, "OriginID", FGFT_GUID);
     276           6 :         FETCH_FIELD_IDX(iDestID, "DestID", FGFT_GUID);
     277             : 
     278          10 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
     279             :              ++iCurFeat)
     280             :         {
     281           5 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
     282           5 :             if (iCurFeat < 0)
     283           0 :                 break;
     284             : 
     285           5 :             const auto psOriginID = oTable.GetFieldValue(iOriginID);
     286           5 :             if (psOriginID && EQUAL(psOriginID->String, osLayerGUID.c_str()))
     287             :             {
     288           4 :                 const auto psDestID = oTable.GetFieldValue(iDestID);
     289           4 :                 if (psDestID && EQUAL(psDestID->String, osDomainUUID.c_str()))
     290             :                 {
     291           1 :                     return true;
     292             :                 }
     293             :             }
     294             :         }
     295             :     }
     296             : 
     297          10 :     return RegisterInItemRelationships(osLayerGUID, osDomainUUID,
     298           5 :                                        pszDomainInDatasetUUID);
     299             : }
     300             : 
     301             : /***********************************************************************/
     302             : /*                      UnlinkDomainToTable()                          */
     303             : /***********************************************************************/
     304             : 
     305           1 : bool OGROpenFileGDBDataSource::UnlinkDomainToTable(
     306             :     const std::string &osDomainName, const std::string &osLayerGUID)
     307             : {
     308           2 :     std::string osDomainUUID;
     309           1 :     if (!FindUUIDFromName(osDomainName, osDomainUUID))
     310           0 :         return false;
     311             : 
     312           2 :     FileGDBTable oTable;
     313           1 :     if (!oTable.Open(m_osGDBItemRelationshipsFilename.c_str(), true))
     314           0 :         return false;
     315             : 
     316           1 :     FETCH_FIELD_IDX(iOriginID, "OriginID", FGFT_GUID);
     317           1 :     FETCH_FIELD_IDX(iDestID, "DestID", FGFT_GUID);
     318             : 
     319           1 :     for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
     320             :          ++iCurFeat)
     321             :     {
     322           1 :         iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
     323           1 :         if (iCurFeat < 0)
     324           0 :             break;
     325             : 
     326           1 :         const auto psOriginID = oTable.GetFieldValue(iOriginID);
     327           1 :         if (psOriginID && EQUAL(psOriginID->String, osLayerGUID.c_str()))
     328             :         {
     329           1 :             const auto psDestID = oTable.GetFieldValue(iDestID);
     330           1 :             if (psDestID && EQUAL(psDestID->String, osDomainUUID.c_str()))
     331             :             {
     332           1 :                 return oTable.DeleteFeature(iCurFeat + 1) && oTable.Sync();
     333             :             }
     334             :         }
     335             :     }
     336             : 
     337           0 :     return true;
     338             : }
     339             : 
     340             : /***********************************************************************/
     341             : /*                    UpdateXMLDefinition()                            */
     342             : /***********************************************************************/
     343             : 
     344          31 : bool OGROpenFileGDBDataSource::UpdateXMLDefinition(
     345             :     const std::string &osLayerName, const char *pszXMLDefinition)
     346             : {
     347          62 :     FileGDBTable oTable;
     348          31 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
     349           0 :         return false;
     350             : 
     351          31 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
     352          31 :     FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
     353             : 
     354         105 :     for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
     355             :          ++iCurFeat)
     356             :     {
     357         105 :         iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
     358         105 :         if (iCurFeat < 0)
     359           0 :             break;
     360         105 :         const auto psName = oTable.GetFieldValue(iName);
     361         105 :         if (psName && psName->String == osLayerName)
     362             :         {
     363          31 :             auto asFields = oTable.GetAllFieldValues();
     364          62 :             if (!OGR_RawField_IsNull(&asFields[iDefinition]) &&
     365          31 :                 !OGR_RawField_IsUnset(&asFields[iDefinition]))
     366             :             {
     367          31 :                 CPLFree(asFields[iDefinition].String);
     368             :             }
     369          31 :             asFields[iDefinition].String = CPLStrdup(pszXMLDefinition);
     370          31 :             bool bRet = oTable.UpdateFeature(iCurFeat + 1, asFields, nullptr);
     371          31 :             oTable.FreeAllFieldValues(asFields);
     372          31 :             return bRet;
     373             :         }
     374             :     }
     375             : 
     376           0 :     CPLError(CE_Failure, CPLE_AppDefined,
     377             :              "Cannot find record for Name=%s in GDB_Items table",
     378             :              osLayerName.c_str());
     379           0 :     return false;
     380             : }
     381             : 
     382             : /***********************************************************************/
     383             : /*                    FindUUIDFromName()                               */
     384             : /***********************************************************************/
     385             : 
     386          42 : bool OGROpenFileGDBDataSource::FindUUIDFromName(const std::string &osName,
     387             :                                                 std::string &osUUIDOut)
     388             : {
     389          84 :     FileGDBTable oTable;
     390          42 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
     391           0 :         return false;
     392             : 
     393          42 :     FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
     394          42 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
     395             : 
     396         296 :     for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
     397             :          ++iCurFeat)
     398             :     {
     399         295 :         iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
     400         295 :         if (iCurFeat < 0)
     401           0 :             break;
     402         295 :         const auto psName = oTable.GetFieldValue(iName);
     403         295 :         if (psName && psName->String == osName)
     404             :         {
     405          41 :             const auto psUUID = oTable.GetFieldValue(iUUID);
     406          41 :             if (psUUID)
     407             :             {
     408          41 :                 osUUIDOut = psUUID->String;
     409          41 :                 return true;
     410             :             }
     411             :         }
     412             :     }
     413             : 
     414           1 :     return false;
     415             : }
     416             : 
     417             : /***********************************************************************/
     418             : /*                  RegisterFeatureDatasetInItems()                    */
     419             : /***********************************************************************/
     420             : 
     421           3 : bool OGROpenFileGDBDataSource::RegisterFeatureDatasetInItems(
     422             :     const std::string &osFeatureDatasetGUID, const std::string &osName,
     423             :     const char *pszXMLDefinition)
     424             : {
     425           6 :     FileGDBTable oTable;
     426           3 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
     427           0 :         return false;
     428             : 
     429           3 :     FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
     430           3 :     FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
     431           3 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
     432           3 :     FETCH_FIELD_IDX(iPhysicalName, "PhysicalName", FGFT_STRING);
     433           3 :     FETCH_FIELD_IDX(iPath, "Path", FGFT_STRING);
     434           3 :     FETCH_FIELD_IDX(iURL, "URL", FGFT_STRING);
     435           3 :     FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
     436           3 :     FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
     437             : 
     438           3 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     439           9 :                                  FileGDBField::UNSET_FIELD);
     440           3 :     fields[iUUID].String = const_cast<char *>(osFeatureDatasetGUID.c_str());
     441           3 :     fields[iType].String = const_cast<char *>(pszFeatureDatasetTypeUUID);
     442           3 :     fields[iName].String = const_cast<char *>(osName.c_str());
     443           6 :     CPLString osUCName(osName);
     444           3 :     osUCName.toupper();
     445           3 :     fields[iPhysicalName].String = const_cast<char *>(osUCName.c_str());
     446           3 :     std::string osPath("\\");
     447           3 :     osPath += osName;
     448           3 :     fields[iPath].String = const_cast<char *>(osPath.c_str());
     449           3 :     fields[iURL].String = const_cast<char *>("");
     450           3 :     fields[iDefinition].String = const_cast<char *>(pszXMLDefinition);
     451           3 :     fields[iProperties].Integer = 1;
     452           3 :     return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
     453             : }
     454             : 
     455             : /***********************************************************************/
     456             : /*                  RegisterFeatureClassInItems()                      */
     457             : /***********************************************************************/
     458             : 
     459         196 : bool OGROpenFileGDBDataSource::RegisterFeatureClassInItems(
     460             :     const std::string &osLayerGUID, const std::string &osLayerName,
     461             :     const std::string &osPath, const FileGDBTable *poLyrTable,
     462             :     const char *pszXMLDefinition, const char *pszDocumentation)
     463             : {
     464         392 :     FileGDBTable oTable;
     465         196 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
     466           0 :         return false;
     467             : 
     468         196 :     FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
     469         196 :     FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
     470         196 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
     471         196 :     FETCH_FIELD_IDX(iPhysicalName, "PhysicalName", FGFT_STRING);
     472         196 :     FETCH_FIELD_IDX(iPath, "Path", FGFT_STRING);
     473         196 :     FETCH_FIELD_IDX(iDatasetSubtype1, "DatasetSubtype1", FGFT_INT32);
     474         196 :     FETCH_FIELD_IDX(iDatasetSubtype2, "DatasetSubtype2", FGFT_INT32);
     475         196 :     FETCH_FIELD_IDX(iDatasetInfo1, "DatasetInfo1", FGFT_STRING);
     476         196 :     FETCH_FIELD_IDX(iURL, "URL", FGFT_STRING);
     477         196 :     FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
     478         196 :     FETCH_FIELD_IDX(iDocumentation, "Documentation", FGFT_XML);
     479         196 :     FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
     480             : 
     481         196 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     482         588 :                                  FileGDBField::UNSET_FIELD);
     483         196 :     fields[iUUID].String = const_cast<char *>(osLayerGUID.c_str());
     484         196 :     fields[iType].String = const_cast<char *>(pszFeatureClassTypeUUID);
     485         196 :     fields[iName].String = const_cast<char *>(osLayerName.c_str());
     486         196 :     CPLString osUCName(osLayerName);
     487         196 :     osUCName.toupper();
     488         196 :     fields[iPhysicalName].String = const_cast<char *>(osUCName.c_str());
     489         196 :     fields[iPath].String = const_cast<char *>(osPath.c_str());
     490         196 :     fields[iDatasetSubtype1].Integer = 1;
     491         196 :     fields[iDatasetSubtype2].Integer = poLyrTable->GetGeometryType();
     492         196 :     const auto poGeomFieldDefn = poLyrTable->GetGeomField();
     493         196 :     if (poGeomFieldDefn)  // should always be true
     494         195 :         fields[iDatasetInfo1].String =
     495         195 :             const_cast<char *>(poGeomFieldDefn->GetName().c_str());
     496         196 :     fields[iURL].String = const_cast<char *>("");
     497         196 :     fields[iDefinition].String = const_cast<char *>(pszXMLDefinition);
     498         196 :     if (pszDocumentation && pszDocumentation[0])
     499           1 :         fields[iDocumentation].String = const_cast<char *>(pszDocumentation);
     500         196 :     fields[iProperties].Integer = 1;
     501         196 :     return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
     502             : }
     503             : 
     504             : /***********************************************************************/
     505             : /*                  RegisterASpatialTableInItems()                     */
     506             : /***********************************************************************/
     507             : 
     508          85 : bool OGROpenFileGDBDataSource::RegisterASpatialTableInItems(
     509             :     const std::string &osLayerGUID, const std::string &osLayerName,
     510             :     const std::string &osPath, const char *pszXMLDefinition,
     511             :     const char *pszDocumentation)
     512             : {
     513         170 :     FileGDBTable oTable;
     514          85 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
     515           0 :         return false;
     516             : 
     517          85 :     FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
     518          85 :     FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
     519          85 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
     520          85 :     FETCH_FIELD_IDX(iPhysicalName, "PhysicalName", FGFT_STRING);
     521          85 :     FETCH_FIELD_IDX(iPath, "Path", FGFT_STRING);
     522          85 :     FETCH_FIELD_IDX(iURL, "URL", FGFT_STRING);
     523          85 :     FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
     524          85 :     FETCH_FIELD_IDX(iDocumentation, "Documentation", FGFT_XML);
     525          85 :     FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
     526             : 
     527          85 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     528         255 :                                  FileGDBField::UNSET_FIELD);
     529          85 :     fields[iUUID].String = const_cast<char *>(osLayerGUID.c_str());
     530          85 :     fields[iType].String = const_cast<char *>(pszTableTypeUUID);
     531          85 :     fields[iName].String = const_cast<char *>(osLayerName.c_str());
     532          85 :     CPLString osUCName(osLayerName);
     533          85 :     osUCName.toupper();
     534          85 :     fields[iPhysicalName].String = const_cast<char *>(osUCName.c_str());
     535          85 :     fields[iPath].String = const_cast<char *>(osPath.c_str());
     536          85 :     fields[iURL].String = const_cast<char *>("");
     537          85 :     fields[iDefinition].String = const_cast<char *>(pszXMLDefinition);
     538          85 :     if (pszDocumentation && pszDocumentation[0])
     539           0 :         fields[iDocumentation].String = const_cast<char *>(pszDocumentation);
     540          85 :     fields[iProperties].Integer = 1;
     541          85 :     return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
     542             : }
     543             : 
     544             : /***********************************************************************/
     545             : /*                       CreateGDBSystemCatalog()                      */
     546             : /***********************************************************************/
     547             : 
     548         229 : bool OGROpenFileGDBDataSource::CreateGDBSystemCatalog()
     549             : {
     550             :     // Write GDB_SystemCatalog file
     551             :     m_osGDBSystemCatalogFilename =
     552         229 :         CPLFormFilenameSafe(m_osDirName.c_str(), "a00000001.gdbtable", nullptr);
     553         458 :     FileGDBTable oTable;
     554         229 :     if (!oTable.Create(m_osGDBSystemCatalogFilename.c_str(), 4, FGTGT_NONE,
     555         458 :                        false, false) ||
     556         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     557         229 :             "ID", std::string(), FGFT_OBJECTID,
     558           0 :             /* bNullable = */ false,
     559           0 :             /* bRequired = */ true,
     560         458 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
     561         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     562         229 :             "Name", std::string(), FGFT_STRING,
     563           0 :             /* bNullable = */ false,
     564           0 :             /* bRequired = */ false,
     565        1145 :             /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) ||
     566         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     567         229 :             "FileFormat", std::string(), FGFT_INT32,
     568           0 :             /* bNullable = */ false,
     569           0 :             /* bRequired = */ false,
     570         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)))
     571             :     {
     572           0 :         return false;
     573             :     }
     574             : 
     575         229 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     576         687 :                                  FileGDBField::UNSET_FIELD);
     577             : 
     578        1832 :     for (const auto &pair : std::vector<std::pair<const char *, int>>{
     579             :              {"GDB_SystemCatalog", 0},
     580             :              {"GDB_DBTune", 0},
     581             :              {"GDB_SpatialRefs", 0},
     582             :              {"GDB_Items", 0},
     583             :              {"GDB_ItemTypes", 0},
     584             :              {"GDB_ItemRelationships", 0},
     585             :              {"GDB_ItemRelationshipTypes", 0},
     586        3893 :              {"GDB_ReplicaLog", 2}})
     587             :     {
     588        1832 :         fields[1].String = const_cast<char *>(pair.first);
     589        1832 :         fields[2].Integer = pair.second;
     590        1832 :         if (!oTable.CreateFeature(fields, nullptr))
     591           0 :             return false;
     592             :     }
     593             : 
     594         229 :     m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
     595         229 :         this, m_osGDBSystemCatalogFilename.c_str(), "GDB_SystemCatalog", "", "",
     596         458 :         true));
     597             : 
     598         229 :     return oTable.Sync();
     599             : }
     600             : 
     601             : /***********************************************************************/
     602             : /*                       CreateGDBDBTune()                             */
     603             : /***********************************************************************/
     604             : 
     605         229 : bool OGROpenFileGDBDataSource::CreateGDBDBTune()
     606             : {
     607             :     // Write GDB_DBTune file
     608             :     const std::string osFilename(CPLFormFilenameSafe(
     609         458 :         m_osDirName.c_str(), "a00000002.gdbtable", nullptr));
     610         458 :     FileGDBTable oTable;
     611         229 :     if (!oTable.Create(osFilename.c_str(), 4, FGTGT_NONE, false, false) ||
     612         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     613         229 :             "Keyword", std::string(), FGFT_STRING,
     614           0 :             /* bNullable = */ false,
     615           0 :             /* bRequired = */ false,
     616         458 :             /* bEditable = */ true, 32, FileGDBField::UNSET_FIELD)) ||
     617         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     618         229 :             "ParameterName", std::string(), FGFT_STRING,
     619           0 :             /* bNullable = */ false,
     620           0 :             /* bRequired = */ false,
     621        1145 :             /* bEditable = */ true, 32, FileGDBField::UNSET_FIELD)) ||
     622         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     623         229 :             "ConfigString", std::string(), FGFT_STRING,
     624           0 :             /* bNullable = */ true,
     625           0 :             /* bRequired = */ false,
     626         458 :             /* bEditable = */ true, 2048, FileGDBField::UNSET_FIELD)))
     627             :     {
     628           0 :         return false;
     629             :     }
     630             : 
     631         229 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     632         687 :                                  FileGDBField::UNSET_FIELD);
     633             : 
     634             :     static const struct
     635             :     {
     636             :         const char *pszKeyword;
     637             :         const char *pszParameterName;
     638             :         const char *pszConfigString;
     639             :     } apsData[] = {
     640             :         {"DEFAULTS", "UI_TEXT", "The default datafile configuration."},
     641             :         {"DEFAULTS", "CHARACTER_FORMAT", "UTF8"},
     642             :         {"DEFAULTS", "GEOMETRY_FORMAT", "Compressed"},
     643             :         {"DEFAULTS", "GEOMETRY_STORAGE", "InLine"},
     644             :         {"DEFAULTS", "BLOB_STORAGE", "InLine"},
     645             :         {"DEFAULTS", "MAX_FILE_SIZE", "1TB"},
     646             :         {"DEFAULTS", "RASTER_STORAGE", "InLine"},
     647             :         {"TEXT_UTF16", "UI_TEXT", "The UTF16 text format configuration."},
     648             :         {"TEXT_UTF16", "CHARACTER_FORMAT", "UTF16"},
     649             :         {"MAX_FILE_SIZE_4GB", "UI_TEXT",
     650             :          "The 4GB maximum datafile size configuration."},
     651             :         {"MAX_FILE_SIZE_4GB", "MAX_FILE_SIZE", "4GB"},
     652             :         {"MAX_FILE_SIZE_256TB", "UI_TEXT",
     653             :          "The 256TB maximum datafile size configuration."},
     654             :         {"MAX_FILE_SIZE_256TB", "MAX_FILE_SIZE", "256TB"},
     655             :         {"GEOMETRY_UNCOMPRESSED", "UI_TEXT",
     656             :          "The Uncompressed Geometry configuration."},
     657             :         {"GEOMETRY_UNCOMPRESSED", "GEOMETRY_FORMAT", "Uncompressed"},
     658             :         {"GEOMETRY_OUTOFLINE", "UI_TEXT",
     659             :          "The Outofline Geometry configuration."},
     660             :         {"GEOMETRY_OUTOFLINE", "GEOMETRY_STORAGE", "OutOfLine"},
     661             :         {"BLOB_OUTOFLINE", "UI_TEXT", "The Outofline Blob configuration."},
     662             :         {"BLOB_OUTOFLINE", "BLOB_STORAGE", "OutOfLine"},
     663             :         {"GEOMETRY_AND_BLOB_OUTOFLINE", "UI_TEXT",
     664             :          "The Outofline Geometry and Blob configuration."},
     665             :         {"GEOMETRY_AND_BLOB_OUTOFLINE", "GEOMETRY_STORAGE", "OutOfLine"},
     666             :         {"GEOMETRY_AND_BLOB_OUTOFLINE", "BLOB_STORAGE", "OutOfLine"},
     667             :         {"TERRAIN_DEFAULTS", "UI_TERRAIN_TEXT",
     668             :          "The terrains default configuration."},
     669             :         {"TERRAIN_DEFAULTS", "GEOMETRY_STORAGE", "OutOfLine"},
     670             :         {"TERRAIN_DEFAULTS", "BLOB_STORAGE", "OutOfLine"},
     671             :         {"MOSAICDATASET_DEFAULTS", "UI_MOSAIC_TEXT",
     672             :          "The Outofline Raster and Blob configuration."},
     673             :         {"MOSAICDATASET_DEFAULTS", "RASTER_STORAGE", "OutOfLine"},
     674             :         {"MOSAICDATASET_DEFAULTS", "BLOB_STORAGE", "OutOfLine"},
     675             :         {"MOSAICDATASET_INLINE", "UI_MOSAIC_TEXT",
     676             :          "The mosaic dataset inline configuration."},
     677             :         {"MOSAICDATASET_INLINE", "CHARACTER_FORMAT", "UTF8"},
     678             :         {"MOSAICDATASET_INLINE", "GEOMETRY_FORMAT", "Compressed"},
     679             :         {"MOSAICDATASET_INLINE", "GEOMETRY_STORAGE", "InLine"},
     680             :         {"MOSAICDATASET_INLINE", "BLOB_STORAGE", "InLine"},
     681             :         {"MOSAICDATASET_INLINE", "MAX_FILE_SIZE", "1TB"},
     682             :         {"MOSAICDATASET_INLINE", "RASTER_STORAGE", "InLine"}};
     683             : 
     684        8244 :     for (const auto &record : apsData)
     685             :     {
     686        8015 :         fields[0].String = const_cast<char *>(record.pszKeyword);
     687        8015 :         fields[1].String = const_cast<char *>(record.pszParameterName);
     688        8015 :         fields[2].String = const_cast<char *>(record.pszConfigString);
     689        8015 :         if (!oTable.CreateFeature(fields, nullptr))
     690           0 :             return false;
     691             :     }
     692             : 
     693         229 :     m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
     694         229 :         this, osFilename.c_str(), "GDB_DBTune", "", "", true));
     695             : 
     696         229 :     return oTable.Sync();
     697             : }
     698             : 
     699             : /***********************************************************************/
     700             : /*                       CreateGDBSpatialRefs()                        */
     701             : /***********************************************************************/
     702             : 
     703         229 : bool OGROpenFileGDBDataSource::CreateGDBSpatialRefs()
     704             : {
     705             :     // Write GDB_SpatialRefs file
     706             :     m_osGDBSpatialRefsFilename =
     707         229 :         CPLFormFilenameSafe(m_osDirName.c_str(), "a00000003.gdbtable", nullptr);
     708         458 :     FileGDBTable oTable;
     709         229 :     if (!oTable.Create(m_osGDBSpatialRefsFilename.c_str(), 4, FGTGT_NONE, false,
     710         458 :                        false) ||
     711         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     712         229 :             "ID", std::string(), FGFT_OBJECTID,
     713           0 :             /* bNullable = */ false,
     714           0 :             /* bRequired = */ true,
     715         458 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
     716         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     717         229 :             "SRTEXT", std::string(), FGFT_STRING,
     718           0 :             /* bNullable = */ false,
     719           0 :             /* bRequired = */ false,
     720         458 :             /* bEditable = */ true, 2048, FileGDBField::UNSET_FIELD)) ||
     721         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     722         229 :             "FalseX", std::string(), FGFT_FLOAT64,
     723           0 :             /* bNullable = */ true,
     724           0 :             /* bRequired = */ false,
     725         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     726         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     727         229 :             "FalseY", std::string(), FGFT_FLOAT64,
     728           0 :             /* bNullable = */ true,
     729           0 :             /* bRequired = */ false,
     730         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     731         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     732         229 :             "XYUnits", std::string(), FGFT_FLOAT64,
     733           0 :             /* bNullable = */ true,
     734           0 :             /* bRequired = */ false,
     735         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     736         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     737         229 :             "FalseZ", std::string(), FGFT_FLOAT64,
     738           0 :             /* bNullable = */ true,
     739           0 :             /* bRequired = */ false,
     740         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     741         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     742         229 :             "ZUnits", std::string(), FGFT_FLOAT64,
     743           0 :             /* bNullable = */ true,
     744           0 :             /* bRequired = */ false,
     745         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     746         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     747         229 :             "FalseM", std::string(), FGFT_FLOAT64,
     748           0 :             /* bNullable = */ true,
     749           0 :             /* bRequired = */ false,
     750         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     751         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     752         229 :             "MUnits", std::string(), FGFT_FLOAT64,
     753           0 :             /* bNullable = */ true,
     754           0 :             /* bRequired = */ false,
     755         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     756         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     757         229 :             "XYTolerance", std::string(), FGFT_FLOAT64,
     758           0 :             /* bNullable = */ true,
     759           0 :             /* bRequired = */ false,
     760         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     761         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     762         229 :             "ZTolerance", std::string(), FGFT_FLOAT64,
     763           0 :             /* bNullable = */ true,
     764           0 :             /* bRequired = */ false,
     765        1145 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     766         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     767         229 :             "MTolerance", std::string(), FGFT_FLOAT64,
     768           0 :             /* bNullable = */ true,
     769           0 :             /* bRequired = */ false,
     770         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)))
     771             :     {
     772           0 :         return false;
     773             :     }
     774             : 
     775         229 :     m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
     776         229 :         this, m_osGDBSpatialRefsFilename.c_str(), "GDB_SpatialRefs", "", "",
     777         458 :         true));
     778             : 
     779         229 :     return oTable.Sync();
     780             : }
     781             : 
     782             : /***********************************************************************/
     783             : /*                       CreateGDBItems()                              */
     784             : /***********************************************************************/
     785             : 
     786         229 : bool OGROpenFileGDBDataSource::CreateGDBItems()
     787             : {
     788             :     // Write GDB_Items file
     789         229 :     const char *ESRI_WKT_WGS84 =
     790             :         "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
     791             :         "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0."
     792             :         "0174532925199433]]";
     793             :     auto poGeomField = std::unique_ptr<FileGDBGeomField>(
     794             :         new FileGDBGeomField("Shape", "", true, ESRI_WKT_WGS84, -180, -90,
     795         687 :                              1000000, 0.000002, {0.012, 0.4, 12.0}));
     796         229 :     poGeomField->SetZOriginScaleTolerance(-100000, 10000, 0.001);
     797         229 :     poGeomField->SetMOriginScaleTolerance(-100000, 10000, 0.001);
     798             : 
     799         229 :     if (!AddNewSpatialRef(poGeomField->GetWKT(), poGeomField->GetXOrigin(),
     800             :                           poGeomField->GetYOrigin(), poGeomField->GetXYScale(),
     801             :                           poGeomField->GetZOrigin(), poGeomField->GetZScale(),
     802             :                           poGeomField->GetMOrigin(), poGeomField->GetMScale(),
     803             :                           poGeomField->GetXYTolerance(),
     804             :                           poGeomField->GetZTolerance(),
     805             :                           poGeomField->GetMTolerance()))
     806             :     {
     807           0 :         return false;
     808             :     }
     809             : 
     810             :     m_osGDBItemsFilename =
     811         229 :         CPLFormFilenameSafe(m_osDirName.c_str(), "a00000004.gdbtable", nullptr);
     812         458 :     FileGDBTable oTable;
     813         229 :     if (!oTable.Create(m_osGDBItemsFilename.c_str(), 4, FGTGT_POLYGON, false,
     814         458 :                        false) ||
     815         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     816         229 :             "ObjectID", std::string(), FGFT_OBJECTID,
     817           0 :             /* bNullable = */ false,
     818           0 :             /* bRequired = */ true,
     819         458 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
     820         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     821         229 :             "UUID", std::string(), FGFT_GLOBALID,
     822           0 :             /* bNullable = */ false,
     823           0 :             /* bRequired = */ true,
     824         458 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
     825         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     826         229 :             "Type", std::string(), FGFT_GUID,
     827           0 :             /* bNullable = */ false,
     828           0 :             /* bRequired = */ false,
     829         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     830         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     831         229 :             "Name", std::string(), FGFT_STRING,
     832           0 :             /* bNullable = */ true,
     833           0 :             /* bRequired = */ false,
     834         458 :             /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) ||
     835         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     836         229 :             "PhysicalName", std::string(), FGFT_STRING,
     837           0 :             /* bNullable = */ true,
     838           0 :             /* bRequired = */ false,
     839         458 :             /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) ||
     840         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     841         229 :             "Path", std::string(), FGFT_STRING,
     842           0 :             /* bNullable = */ true,
     843           0 :             /* bRequired = */ false,
     844         458 :             /* bEditable = */ true, 260, FileGDBField::UNSET_FIELD)) ||
     845         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     846         229 :             "DatasetSubtype1", std::string(), FGFT_INT32,
     847           0 :             /* bNullable = */ true,
     848           0 :             /* bRequired = */ false,
     849         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     850         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     851         229 :             "DatasetSubtype2", std::string(), FGFT_INT32,
     852           0 :             /* bNullable = */ true,
     853           0 :             /* bRequired = */ false,
     854         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     855         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     856         229 :             "DatasetInfo1", std::string(), FGFT_STRING,
     857           0 :             /* bNullable = */ true,
     858           0 :             /* bRequired = */ false,
     859         458 :             /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) ||
     860         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     861         229 :             "DatasetInfo2", std::string(), FGFT_STRING,
     862           0 :             /* bNullable = */ true,
     863           0 :             /* bRequired = */ false,
     864         458 :             /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) ||
     865         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     866         229 :             "URL", std::string(), FGFT_STRING,
     867           0 :             /* bNullable = */ true,
     868           0 :             /* bRequired = */ false,
     869         458 :             /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) ||
     870         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     871         229 :             "Definition", std::string(), FGFT_XML,
     872           0 :             /* bNullable = */ true,
     873           0 :             /* bRequired = */ false,
     874         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     875         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     876         229 :             "Documentation", std::string(), FGFT_XML,
     877           0 :             /* bNullable = */ true,
     878           0 :             /* bRequired = */ false,
     879         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     880         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     881         229 :             "ItemInfo", std::string(), FGFT_XML,
     882           0 :             /* bNullable = */ true,
     883           0 :             /* bRequired = */ false,
     884         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     885         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     886         229 :             "Properties", std::string(), FGFT_INT32,
     887           0 :             /* bNullable = */ true,
     888           0 :             /* bRequired = */ false,
     889         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     890         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     891         229 :             "Defaults", std::string(), FGFT_BINARY,
     892           0 :             /* bNullable = */ true,
     893           0 :             /* bRequired = */ false,
     894        1145 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     895         458 :         !oTable.CreateField(std::move(poGeomField)))
     896             :     {
     897           0 :         return false;
     898             :     }
     899             : 
     900         229 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     901         687 :                                  FileGDBField::UNSET_FIELD);
     902         229 :     m_osRootGUID = OFGDBGenerateUUID();
     903         229 :     fields[1].String = const_cast<char *>(m_osRootGUID.c_str());
     904         229 :     fields[2].String = const_cast<char *>(pszFolderTypeUUID);
     905         229 :     fields[3].String = const_cast<char *>("");
     906         229 :     fields[4].String = const_cast<char *>("");
     907         229 :     fields[5].String = const_cast<char *>("\\");
     908         229 :     fields[10].String = const_cast<char *>("");
     909         229 :     fields[14].Integer = 1;
     910         229 :     if (!oTable.CreateFeature(fields, nullptr))
     911           0 :         return false;
     912             : 
     913         229 :     const std::string osWorkspaceUUID(OFGDBGenerateUUID());
     914         229 :     fields[1].String = const_cast<char *>(osWorkspaceUUID.c_str());
     915         229 :     fields[2].String = const_cast<char *>(pszWorkspaceTypeUUID);
     916         229 :     fields[3].String = const_cast<char *>("Workspace");
     917         229 :     fields[4].String = const_cast<char *>("WORKSPACE");
     918         229 :     fields[5].String = const_cast<char *>("");   // Path
     919         229 :     fields[10].String = const_cast<char *>("");  // URL
     920         229 :     fields[11].String = const_cast<char *>(
     921             :         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
     922             :         "<DEWorkspace xmlns:typens=\"http://www.esri.com/schemas/ArcGIS/10.3\" "
     923             :         "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
     924             :         "xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" "
     925             :         "xsi:type=\"typens:DEWorkspace\">\n"
     926             :         "  <CatalogPath>\\</CatalogPath>\n"
     927             :         "  <Name/>\n"
     928             :         "  <ChildrenExpanded>false</ChildrenExpanded>\n"
     929             :         "  <WorkspaceType>esriLocalDatabaseWorkspace</WorkspaceType>\n"
     930             :         "  <WorkspaceFactoryProgID/>\n"
     931             :         "  <ConnectionString/>\n"
     932             :         "  <ConnectionInfo xsi:nil=\"true\"/>\n"
     933             :         "  <Domains xsi:type=\"typens:ArrayOfDomain\"/>\n"
     934             :         "  <MajorVersion>3</MajorVersion>\n"
     935             :         "  <MinorVersion>0</MinorVersion>\n"
     936             :         "  <BugfixVersion>0</BugfixVersion>\n"
     937             :         "</DEWorkspace>");
     938         229 :     fields[14].Integer = 0;
     939             : 
     940         229 :     m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
     941         229 :         this, m_osGDBItemsFilename.c_str(), "GDB_Items", "", "", true));
     942             : 
     943         229 :     return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
     944             : }
     945             : 
     946             : /***********************************************************************/
     947             : /*                       CreateGDBItemTypes()                          */
     948             : /***********************************************************************/
     949             : 
     950         229 : bool OGROpenFileGDBDataSource::CreateGDBItemTypes()
     951             : {
     952             :     // Write GDB_ItemTypes file
     953             :     const std::string osFilename(CPLFormFilenameSafe(
     954         458 :         m_osDirName.c_str(), "a00000005.gdbtable", nullptr));
     955         458 :     FileGDBTable oTable;
     956         229 :     if (!oTable.Create(osFilename.c_str(), 4, FGTGT_NONE, false, false) ||
     957         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     958         229 :             "ObjectID", std::string(), FGFT_OBJECTID,
     959           0 :             /* bNullable = */ false,
     960           0 :             /* bRequired = */ true,
     961         458 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
     962         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     963         229 :             "UUID", std::string(), FGFT_GUID,
     964           0 :             /* bNullable = */ false,
     965           0 :             /* bRequired = */ false,
     966         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     967         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     968         229 :             "ParentTypeID", std::string(), FGFT_GUID,
     969           0 :             /* bNullable = */ false,
     970           0 :             /* bRequired = */ false,
     971        1145 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     972         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     973         229 :             "Name", std::string(), FGFT_STRING,
     974           0 :             /* bNullable = */ false,
     975           0 :             /* bRequired = */ false,
     976         458 :             /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)))
     977             :     {
     978           0 :         return false;
     979             :     }
     980             : 
     981         229 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     982         687 :                                  FileGDBField::UNSET_FIELD);
     983             : 
     984             :     static const struct
     985             :     {
     986             :         const char *pszUUID;
     987             :         const char *pszParentTypeID;
     988             :         const char *pszName;
     989             :     } apsData[] = {
     990             :         {"{8405add5-8df8-4227-8fac-3fcade073386}",
     991             :          "{00000000-0000-0000-0000-000000000000}", "Item"},
     992             :         {pszFolderTypeUUID, "{8405add5-8df8-4227-8fac-3fcade073386}", "Folder"},
     993             :         {"{ffd09c28-fe70-4e25-907c-af8e8a5ec5f3}",
     994             :          "{8405add5-8df8-4227-8fac-3fcade073386}", "Resource"},
     995             :         {"{28da9e89-ff80-4d6d-8926-4ee2b161677d}",
     996             :          "{ffd09c28-fe70-4e25-907c-af8e8a5ec5f3}", "Dataset"},
     997             :         {"{fbdd7dd6-4a25-40b7-9a1a-ecc3d1172447}",
     998             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Tin"},
     999             :         {"{d4912162-3413-476e-9da4-2aefbbc16939}",
    1000             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "AbstractTable"},
    1001             :         {"{b606a7e1-fa5b-439c-849c-6e9c2481537b}",
    1002             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Relationship Class"},
    1003             :         {pszFeatureDatasetTypeUUID, "{28da9e89-ff80-4d6d-8926-4ee2b161677d}",
    1004             :          "Feature Dataset"},
    1005             :         {"{73718a66-afb9-4b88-a551-cffa0ae12620}",
    1006             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Geometric Network"},
    1007             :         {"{767152d3-ed66-4325-8774-420d46674e07}",
    1008             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Topology"},
    1009             :         {"{e6302665-416b-44fa-be33-4e15916ba101}",
    1010             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Survey Dataset"},
    1011             :         {"{d5a40288-029e-4766-8c81-de3f61129371}",
    1012             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Schematic Dataset"},
    1013             :         {"{db1b697a-3bb6-426a-98a2-6ee7a4c6aed3}",
    1014             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Toolbox"},
    1015             :         {pszWorkspaceTypeUUID, "{28da9e89-ff80-4d6d-8926-4ee2b161677d}",
    1016             :          "Workspace"},
    1017             :         {"{dc9ef677-1aa3-45a7-8acd-303a5202d0dc}",
    1018             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Workspace Extension"},
    1019             :         {"{77292603-930f-475d-ae4f-b8970f42f394}",
    1020             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Extension Dataset"},
    1021             :         {"{8637f1ed-8c04-4866-a44a-1cb8288b3c63}",
    1022             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Domain"},
    1023             :         {"{4ed4a58e-621f-4043-95ed-850fba45fcbc}",
    1024             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Replica"},
    1025             :         {"{d98421eb-d582-4713-9484-43304d0810f6}",
    1026             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Replica Dataset"},
    1027             :         {"{dc64b6e4-dc0f-43bd-b4f5-f22385dcf055}",
    1028             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Historical Marker"},
    1029             :         {pszTableTypeUUID, "{d4912162-3413-476e-9da4-2aefbbc16939}", "Table"},
    1030             :         {pszFeatureClassTypeUUID, "{d4912162-3413-476e-9da4-2aefbbc16939}",
    1031             :          "Feature Class"},
    1032             :         {"{5ed667a3-9ca9-44a2-8029-d95bf23704b9}",
    1033             :          "{d4912162-3413-476e-9da4-2aefbbc16939}", "Raster Dataset"},
    1034             :         {"{35b601f7-45ce-4aff-adb7-7702d3839b12}",
    1035             :          "{d4912162-3413-476e-9da4-2aefbbc16939}", "Raster Catalog"},
    1036             :         {"{7771fc7d-a38b-4fd3-8225-639d17e9a131}",
    1037             :          "{77292603-930f-475d-ae4f-b8970f42f394}", "Network Dataset"},
    1038             :         {"{76357537-3364-48af-a4be-783c7c28b5cb}",
    1039             :          "{77292603-930f-475d-ae4f-b8970f42f394}", "Terrain"},
    1040             :         {"{a3803369-5fc2-4963-bae0-13effc09dd73}",
    1041             :          "{77292603-930f-475d-ae4f-b8970f42f394}", "Parcel Fabric"},
    1042             :         {"{a300008d-0cea-4f6a-9dfa-46af829a3df2}",
    1043             :          "{77292603-930f-475d-ae4f-b8970f42f394}", "Representation Class"},
    1044             :         {"{787bea35-4a86-494f-bb48-500b96145b58}",
    1045             :          "{77292603-930f-475d-ae4f-b8970f42f394}", "Catalog Dataset"},
    1046             :         {"{f8413dcb-2248-4935-bfe9-315f397e5110}",
    1047             :          "{77292603-930f-475d-ae4f-b8970f42f394}", "Mosaic Dataset"},
    1048             :         {pszRangeDomainTypeUUID, "{8637f1ed-8c04-4866-a44a-1cb8288b3c63}",
    1049             :          "Range Domain"},
    1050             :         {pszCodedDomainTypeUUID, "{8637f1ed-8c04-4866-a44a-1cb8288b3c63}",
    1051             :          "Coded Value Domain"}};
    1052             : 
    1053        7557 :     for (const auto &record : apsData)
    1054             :     {
    1055        7328 :         fields[1].String = const_cast<char *>(record.pszUUID);
    1056        7328 :         fields[2].String = const_cast<char *>(record.pszParentTypeID);
    1057        7328 :         fields[3].String = const_cast<char *>(record.pszName);
    1058        7328 :         if (!oTable.CreateFeature(fields, nullptr))
    1059           0 :             return false;
    1060             :     }
    1061             : 
    1062         229 :     m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
    1063         229 :         this, osFilename.c_str(), "GDB_ItemTypes", "", "", true));
    1064             : 
    1065         229 :     return oTable.Sync();
    1066             : }
    1067             : 
    1068             : /***********************************************************************/
    1069             : /*                  CreateGDBItemRelationships()                       */
    1070             : /***********************************************************************/
    1071             : 
    1072         229 : bool OGROpenFileGDBDataSource::CreateGDBItemRelationships()
    1073             : {
    1074             :     // Write GDB_ItemRelationships file
    1075             :     m_osGDBItemRelationshipsFilename =
    1076         229 :         CPLFormFilenameSafe(m_osDirName.c_str(), "a00000006.gdbtable", nullptr);
    1077         458 :     FileGDBTable oTable;
    1078         229 :     if (!oTable.Create(m_osGDBItemRelationshipsFilename.c_str(), 4, FGTGT_NONE,
    1079         458 :                        false, false) ||
    1080         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1081         229 :             "ObjectID", std::string(), FGFT_OBJECTID,
    1082           0 :             /* bNullable = */ false,
    1083           0 :             /* bRequired = */ true,
    1084         458 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
    1085         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1086         229 :             "UUID", std::string(), FGFT_GLOBALID,
    1087           0 :             /* bNullable = */ false,
    1088           0 :             /* bRequired = */ true,
    1089         458 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
    1090         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1091         229 :             "OriginID", std::string(), FGFT_GUID,
    1092           0 :             /* bNullable = */ false,
    1093           0 :             /* bRequired = */ false,
    1094         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
    1095         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1096         229 :             "DestID", std::string(), FGFT_GUID,
    1097           0 :             /* bNullable = */ false,
    1098           0 :             /* bRequired = */ false,
    1099         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
    1100         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1101         229 :             "Type", std::string(), FGFT_GUID,
    1102           0 :             /* bNullable = */ false,
    1103           0 :             /* bRequired = */ false,
    1104         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
    1105         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1106         229 :             "Attributes", std::string(), FGFT_XML,
    1107           0 :             /* bNullable = */ true,
    1108           0 :             /* bRequired = */ false,
    1109        1145 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
    1110         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1111         229 :             "Properties", std::string(), FGFT_INT32,
    1112           0 :             /* bNullable = */ true,
    1113           0 :             /* bRequired = */ false,
    1114         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)))
    1115             :     {
    1116           0 :         return false;
    1117             :     }
    1118             : 
    1119         229 :     m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
    1120         229 :         this, m_osGDBItemRelationshipsFilename.c_str(), "GDB_ItemRelationships",
    1121         458 :         "", "", true));
    1122             : 
    1123         229 :     return oTable.Sync();
    1124             : }
    1125             : 
    1126             : /***********************************************************************/
    1127             : /*                 CreateGDBItemRelationshipTypes()                    */
    1128             : /***********************************************************************/
    1129             : 
    1130         229 : bool OGROpenFileGDBDataSource::CreateGDBItemRelationshipTypes()
    1131             : {
    1132             :     // Write GDB_ItemRelationshipTypes file
    1133             :     const std::string osFilename(CPLFormFilenameSafe(
    1134         458 :         m_osDirName.c_str(), "a00000007.gdbtable", nullptr));
    1135         458 :     FileGDBTable oTable;
    1136         229 :     if (!oTable.Create(osFilename.c_str(), 4, FGTGT_NONE, false, false) ||
    1137         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1138         229 :             "ObjectID", std::string(), FGFT_OBJECTID,
    1139           0 :             /* bNullable = */ false,
    1140           0 :             /* bRequired = */ true,
    1141         458 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
    1142         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1143         229 :             "UUID", std::string(), FGFT_GUID,
    1144           0 :             /* bNullable = */ false,
    1145           0 :             /* bRequired = */ false,
    1146         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
    1147         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1148         229 :             "OrigItemTypeID", std::string(), FGFT_GUID,
    1149           0 :             /* bNullable = */ false,
    1150           0 :             /* bRequired = */ false,
    1151         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
    1152         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1153         229 :             "DestItemTypeID", std::string(), FGFT_GUID,
    1154           0 :             /* bNullable = */ false,
    1155           0 :             /* bRequired = */ false,
    1156         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
    1157         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1158         229 :             "Name", std::string(), FGFT_STRING,
    1159           0 :             /* bNullable = */ true,
    1160           0 :             /* bRequired = */ false,
    1161         458 :             /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) ||
    1162         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1163         229 :             "ForwardLabel", std::string(), FGFT_STRING,
    1164           0 :             /* bNullable = */ true,
    1165           0 :             /* bRequired = */ false,
    1166         458 :             /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) ||
    1167         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1168         229 :             "BackwardLabel", std::string(), FGFT_STRING,
    1169           0 :             /* bNullable = */ true,
    1170           0 :             /* bRequired = */ false,
    1171        1145 :             /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) ||
    1172         458 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1173         229 :             "IsContainment", std::string(), FGFT_INT16,
    1174           0 :             /* bNullable = */ true,
    1175           0 :             /* bRequired = */ false,
    1176         458 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)))
    1177             :     {
    1178           0 :         return false;
    1179             :     }
    1180             : 
    1181             :     static const struct
    1182             :     {
    1183             :         const char *pszUUID;
    1184             :         const char *pszOrigItemTypeID;
    1185             :         const char *pszDestItemTypeID;
    1186             :         const char *pszName;
    1187             :         const char *pszForwardLabel;
    1188             :         const char *pszBackwardLabel;
    1189             :         int IsContainment;
    1190             :     } apsData[] = {
    1191             :         {"{0d10b3a7-2f64-45e6-b7ac-2fc27bf2133c}", pszFolderTypeUUID,
    1192             :          pszFolderTypeUUID, "FolderInFolder", "Parent Folder Of",
    1193             :          "Child Folder Of", 1},
    1194             :         {"{5dd0c1af-cb3d-4fea-8c51-cb3ba8d77cdb}", pszFolderTypeUUID,
    1195             :          "{8405add5-8df8-4227-8fac-3fcade073386}", "ItemInFolder",
    1196             :          "Contains Item", "Contained In Folder", 1},
    1197             :         {pszDatasetInFeatureDatasetUUID, pszFeatureDatasetTypeUUID,
    1198             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "DatasetInFeatureDataset",
    1199             :          "Contains Dataset", "Contained In FeatureDataset", 1},
    1200             :         {pszDatasetInFolderUUID, pszFolderTypeUUID,
    1201             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "DatasetInFolder",
    1202             :          "Contains Dataset", "Contained in Dataset", 1},
    1203             :         {pszDomainInDatasetUUID, "{28da9e89-ff80-4d6d-8926-4ee2b161677d}",
    1204             :          "{8637f1ed-8c04-4866-a44a-1cb8288b3c63}", "DomainInDataset",
    1205             :          "Contains Domain", "Contained in Dataset", 0},
    1206             :         {"{725badab-3452-491b-a795-55f32d67229c}",
    1207             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}",
    1208             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "DatasetsRelatedThrough",
    1209             :          "Origin Of", "Destination Of", 0},
    1210             :         {"{d088b110-190b-4229-bdf7-89fddd14d1ea}",
    1211             :          "{767152d3-ed66-4325-8774-420d46674e07}", pszFeatureClassTypeUUID,
    1212             :          "FeatureClassInTopology", "Spatially Manages Feature Class",
    1213             :          "Participates In Topology", 0},
    1214             :         {"{dc739a70-9b71-41e8-868c-008cf46f16d7}",
    1215             :          "{73718a66-afb9-4b88-a551-cffa0ae12620}", pszFeatureClassTypeUUID,
    1216             :          "FeatureClassInGeometricNetwork", "Spatially Manages Feature Class",
    1217             :          "Participates In Geometric Network", 0},
    1218             :         {"{b32b8563-0b96-4d32-92c4-086423ae9962}",
    1219             :          "{7771fc7d-a38b-4fd3-8225-639d17e9a131}", pszFeatureClassTypeUUID,
    1220             :          "FeatureClassInNetworkDataset", "Spatially Manages Feature Class",
    1221             :          "Participates In Network Dataset", 0},
    1222             :         {"{908a4670-1111-48c6-8269-134fdd3fe617}",
    1223             :          "{7771fc7d-a38b-4fd3-8225-639d17e9a131}", pszTableTypeUUID,
    1224             :          "TableInNetworkDataset", "Manages Table",
    1225             :          "Participates In Network Dataset", 0},
    1226             :         {"{55d2f4dc-cb17-4e32-a8c7-47591e8c71de}",
    1227             :          "{76357537-3364-48af-a4be-783c7c28b5cb}", pszFeatureClassTypeUUID,
    1228             :          "FeatureClassInTerrain", "Spatially Manages Feature Class",
    1229             :          "Participates In Terrain", 0},
    1230             :         {"{583a5baa-3551-41ae-8aa8-1185719f3889}",
    1231             :          "{a3803369-5fc2-4963-bae0-13effc09dd73}", pszFeatureClassTypeUUID,
    1232             :          "FeatureClassInParcelFabric", "Spatially Manages Feature Class",
    1233             :          "Participates In Parcel Fabric", 0},
    1234             :         {"{5f9085e0-788f-4354-ae3c-34c83a7ea784}",
    1235             :          "{a3803369-5fc2-4963-bae0-13effc09dd73}", pszTableTypeUUID,
    1236             :          "TableInParcelFabric", "Manages Table",
    1237             :          "Participates In Parcel Fabric", 0},
    1238             :         {"{e79b44e3-f833-4b12-90a1-364ec4ddc43e}", pszFeatureClassTypeUUID,
    1239             :          "{a300008d-0cea-4f6a-9dfa-46af829a3df2}",
    1240             :          "RepresentationOfFeatureClass", "Feature Class Representation",
    1241             :          "Represented Feature Class", 0},
    1242             :         {"{8db31af1-df7c-4632-aa10-3cc44b0c6914}",
    1243             :          "{4ed4a58e-621f-4043-95ed-850fba45fcbc}",
    1244             :          "{d98421eb-d582-4713-9484-43304d0810f6}", "ReplicaDatasetInReplica",
    1245             :          "Replicated Dataset", "Participates In Replica", 1},
    1246             :         {"{d022de33-45bd-424c-88bf-5b1b6b957bd3}",
    1247             :          "{d98421eb-d582-4713-9484-43304d0810f6}",
    1248             :          "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "DatasetOfReplicaDataset",
    1249             :          "Replicated Dataset", "Dataset of Replicated Dataset", 0},
    1250             :     };
    1251             : 
    1252         229 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
    1253         687 :                                  FileGDBField::UNSET_FIELD);
    1254        3893 :     for (const auto &record : apsData)
    1255             :     {
    1256        3664 :         fields[1].String = const_cast<char *>(record.pszUUID);
    1257        3664 :         fields[2].String = const_cast<char *>(record.pszOrigItemTypeID);
    1258        3664 :         fields[3].String = const_cast<char *>(record.pszDestItemTypeID);
    1259        3664 :         fields[4].String = const_cast<char *>(record.pszName);
    1260        3664 :         fields[5].String = const_cast<char *>(record.pszForwardLabel);
    1261        3664 :         fields[6].String = const_cast<char *>(record.pszBackwardLabel);
    1262        3664 :         fields[7].Integer = record.IsContainment;
    1263        3664 :         if (!oTable.CreateFeature(fields, nullptr))
    1264           0 :             return false;
    1265             :     }
    1266             : 
    1267         229 :     m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
    1268         229 :         this, osFilename.c_str(), "GDB_ItemRelationshipTypes", "", "", true));
    1269             : 
    1270         229 :     return oTable.Sync();
    1271             : }
    1272             : 
    1273             : /***********************************************************************/
    1274             : /*                             Create()                                */
    1275             : /***********************************************************************/
    1276             : 
    1277         232 : bool OGROpenFileGDBDataSource::Create(const char *pszName)
    1278             : {
    1279             : 
    1280         232 :     if (!EQUAL(CPLGetExtensionSafe(pszName).c_str(), "gdb"))
    1281             :     {
    1282           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    1283             :                  "Extension of the directory should be gdb");
    1284           1 :         return false;
    1285             :     }
    1286             : 
    1287             :     /* Don't try to create on top of something already there */
    1288             :     VSIStatBufL sStat;
    1289         231 :     if (VSIStatL(pszName, &sStat) == 0)
    1290             :     {
    1291           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s already exists.", pszName);
    1292           0 :         return false;
    1293             :     }
    1294             : 
    1295         231 :     if (VSIMkdir(pszName, 0755) != 0)
    1296             :     {
    1297           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot create directory %s.",
    1298             :                  pszName);
    1299           2 :         return false;
    1300             :     }
    1301             : 
    1302         229 :     CPL_IGNORE_RET_VAL(OFGDBGenerateUUID(/* bInit = */ true));
    1303             : 
    1304         229 :     m_osDirName = pszName;
    1305         229 :     eAccess = GA_Update;
    1306             : 
    1307             :     {
    1308             :         // Write "gdb" file
    1309             :         const std::string osFilename(
    1310         229 :             CPLFormFilenameSafe(pszName, "gdb", nullptr));
    1311         229 :         VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
    1312         229 :         if (!fp)
    1313           0 :             return false;
    1314             :         // Write what the FileGDB SDK writes...
    1315         229 :         VSIFWriteL("\x05\x00\x00\x00\xDE\xAD\xBE\xEF", 1, 8, fp);
    1316         229 :         VSIFCloseL(fp);
    1317             :     }
    1318             : 
    1319             :     {
    1320             :         // Write "timestamps" file
    1321             :         const std::string osFilename(
    1322         229 :             CPLFormFilenameSafe(pszName, "timestamps", nullptr));
    1323         229 :         VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
    1324         229 :         if (!fp)
    1325           0 :             return false;
    1326             :         // Write what the FileGDB SDK writes...
    1327         458 :         std::vector<GByte> values(400, 0xFF);
    1328         229 :         VSIFWriteL(values.data(), 1, values.size(), fp);
    1329         229 :         VSIFCloseL(fp);
    1330             :     }
    1331             : 
    1332         458 :     return CreateGDBSystemCatalog() && CreateGDBDBTune() &&
    1333         229 :            CreateGDBSpatialRefs() && CreateGDBItems() && CreateGDBItemTypes() &&
    1334         458 :            CreateGDBItemRelationships() && CreateGDBItemRelationshipTypes();
    1335             :     // GDB_ReplicaLog can be omitted.
    1336             : }
    1337             : 
    1338             : /************************************************************************/
    1339             : /*                             ICreateLayer()                           */
    1340             : /************************************************************************/
    1341             : 
    1342             : OGRLayer *
    1343         283 : OGROpenFileGDBDataSource::ICreateLayer(const char *pszLayerName,
    1344             :                                        const OGRGeomFieldDefn *poGeomFieldDefn,
    1345             :                                        CSLConstList papszOptions)
    1346             : {
    1347         283 :     if (eAccess != GA_Update)
    1348           0 :         return nullptr;
    1349             : 
    1350         283 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    1351           0 :         return nullptr;
    1352             : 
    1353         283 :     if (m_osRootGUID.empty())
    1354             :     {
    1355           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Root UUID missing");
    1356           0 :         return nullptr;
    1357             :     }
    1358             : 
    1359         283 :     auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
    1360             : 
    1361         566 :     FileGDBTable oTable;
    1362         566 :     if (!oTable.Open(m_osGDBSystemCatalogFilename.c_str(), false) ||
    1363         283 :         oTable.GetTotalRecordCount() >= INT32_MAX)
    1364           0 :         return nullptr;
    1365         283 :     const int nTableNum = static_cast<int>(1 + oTable.GetTotalRecordCount());
    1366         283 :     oTable.Close();
    1367             : 
    1368             :     const std::string osFilename(CPLFormFilenameSafe(
    1369         566 :         m_osDirName.c_str(), CPLSPrintf("a%08x.gdbtable", nTableNum), nullptr));
    1370             : 
    1371         283 :     if (wkbFlatten(eType) == wkbLineString)
    1372          16 :         eType = OGR_GT_SetModifier(wkbMultiLineString, OGR_GT_HasZ(eType),
    1373             :                                    OGR_GT_HasM(eType));
    1374         267 :     else if (wkbFlatten(eType) == wkbPolygon)
    1375          17 :         eType = OGR_GT_SetModifier(wkbMultiPolygon, OGR_GT_HasZ(eType),
    1376             :                                    OGR_GT_HasM(eType));
    1377             : 
    1378             :     auto poLayer = std::make_unique<OGROpenFileGDBLayer>(
    1379         566 :         this, osFilename.c_str(), pszLayerName, eType, papszOptions);
    1380         283 :     if (!poLayer->Create(poGeomFieldDefn))
    1381           3 :         return nullptr;
    1382         280 :     if (m_bInTransaction)
    1383             :     {
    1384           4 :         if (!poLayer->BeginEmulatedTransaction())
    1385           0 :             return nullptr;
    1386           4 :         m_oSetLayersCreatedInTransaction.insert(poLayer.get());
    1387             :     }
    1388         280 :     m_apoLayers.emplace_back(std::move(poLayer));
    1389             : 
    1390         280 :     return m_apoLayers.back().get();
    1391             : }
    1392             : 
    1393             : /************************************************************************/
    1394             : /*                            DeleteLayer()                             */
    1395             : /************************************************************************/
    1396             : 
    1397          19 : OGRErr OGROpenFileGDBDataSource::DeleteLayer(int iLayer)
    1398             : {
    1399          19 :     if (eAccess != GA_Update)
    1400           0 :         return OGRERR_FAILURE;
    1401             : 
    1402          19 :     if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
    1403           2 :         return OGRERR_FAILURE;
    1404             : 
    1405          17 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    1406           0 :         return false;
    1407             : 
    1408          17 :     auto poLayer = m_apoLayers[iLayer].get();
    1409             : 
    1410             :     // Remove from GDB_SystemCatalog
    1411             :     {
    1412          17 :         FileGDBTable oTable;
    1413          17 :         if (!oTable.Open(m_osGDBSystemCatalogFilename.c_str(), true))
    1414           0 :             return OGRERR_FAILURE;
    1415             : 
    1416          17 :         FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, OGRERR_FAILURE);
    1417             : 
    1418         153 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    1419             :              ++iCurFeat)
    1420             :         {
    1421         153 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    1422         153 :             if (iCurFeat < 0)
    1423           0 :                 break;
    1424         153 :             const auto psName = oTable.GetFieldValue(iName);
    1425         153 :             if (psName && strcmp(psName->String, poLayer->GetName()) == 0)
    1426             :             {
    1427          17 :                 oTable.DeleteFeature(iCurFeat + 1);
    1428          17 :                 break;
    1429             :             }
    1430             :         }
    1431             :     }
    1432             : 
    1433             :     // Remove from GDB_Items
    1434          34 :     std::string osUUID;
    1435             :     {
    1436          17 :         FileGDBTable oTable;
    1437          17 :         if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
    1438           0 :             return OGRERR_FAILURE;
    1439             : 
    1440          17 :         FETCH_FIELD_IDX_WITH_RET(iUUID, "UUID", FGFT_GLOBALID, OGRERR_FAILURE);
    1441          17 :         FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, OGRERR_FAILURE);
    1442             : 
    1443          51 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    1444             :              ++iCurFeat)
    1445             :         {
    1446          37 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    1447          37 :             if (iCurFeat < 0)
    1448           0 :                 break;
    1449          37 :             const auto psName = oTable.GetFieldValue(iName);
    1450          37 :             if (psName && strcmp(psName->String, poLayer->GetName()) == 0)
    1451             :             {
    1452           3 :                 const auto psUUID = oTable.GetFieldValue(iUUID);
    1453           3 :                 if (psUUID)
    1454             :                 {
    1455           3 :                     osUUID = psUUID->String;
    1456             :                 }
    1457             : 
    1458           3 :                 oTable.DeleteFeature(iCurFeat + 1);
    1459           3 :                 break;
    1460             :             }
    1461             :         }
    1462             :     }
    1463             : 
    1464             :     // Remove from GDB_ItemRelationships
    1465          17 :     if (!osUUID.empty())
    1466             :     {
    1467           3 :         FileGDBTable oTable;
    1468           3 :         if (!oTable.Open(m_osGDBItemRelationshipsFilename.c_str(), true))
    1469           0 :             return OGRERR_FAILURE;
    1470             : 
    1471           3 :         FETCH_FIELD_IDX_WITH_RET(iOriginID, "OriginID", FGFT_GUID,
    1472             :                                  OGRERR_FAILURE);
    1473           3 :         FETCH_FIELD_IDX_WITH_RET(iDestID, "DestID", FGFT_GUID, OGRERR_FAILURE);
    1474             : 
    1475           7 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    1476             :              ++iCurFeat)
    1477             :         {
    1478           4 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    1479           4 :             if (iCurFeat < 0)
    1480           0 :                 break;
    1481             : 
    1482           4 :             const auto psOriginID = oTable.GetFieldValue(iOriginID);
    1483           4 :             if (psOriginID && psOriginID->String == osUUID)
    1484             :             {
    1485           0 :                 oTable.DeleteFeature(iCurFeat + 1);
    1486             :             }
    1487             :             else
    1488             :             {
    1489           4 :                 const auto psDestID = oTable.GetFieldValue(iDestID);
    1490           4 :                 if (psDestID && psDestID->String == osUUID)
    1491             :                 {
    1492           3 :                     oTable.DeleteFeature(iCurFeat + 1);
    1493             :                 }
    1494             :             }
    1495             :         }
    1496             :     }
    1497             : 
    1498             :     const std::string osDirname =
    1499          34 :         CPLGetPathSafe(poLayer->GetFilename().c_str());
    1500             :     const std::string osFilenameBase =
    1501          17 :         CPLGetBasenameSafe(poLayer->GetFilename().c_str());
    1502             : 
    1503          17 :     if (m_bInTransaction)
    1504             :     {
    1505             :         auto oIter =
    1506           2 :             m_oSetLayersCreatedInTransaction.find(m_apoLayers[iLayer].get());
    1507           2 :         if (oIter != m_oSetLayersCreatedInTransaction.end())
    1508             :         {
    1509           1 :             m_oSetLayersCreatedInTransaction.erase(oIter);
    1510             :         }
    1511             :         else
    1512             :         {
    1513           1 :             poLayer->BeginEmulatedTransaction();
    1514           1 :             poLayer->Close();
    1515             :             m_oSetLayersDeletedInTransaction.insert(
    1516           1 :                 std::move(m_apoLayers[iLayer]));
    1517             :         }
    1518             :     }
    1519             : 
    1520             :     // Delete OGR layer
    1521          17 :     m_apoLayers.erase(m_apoLayers.begin() + iLayer);
    1522             : 
    1523             :     // Remove files associated with the layer
    1524          17 :     char **papszFiles = VSIReadDir(osDirname.c_str());
    1525         408 :     for (char **papszIter = papszFiles; papszIter && *papszIter; ++papszIter)
    1526             :     {
    1527         391 :         if (STARTS_WITH(*papszIter, osFilenameBase.c_str()))
    1528             :         {
    1529          59 :             VSIUnlink(
    1530         118 :                 CPLFormFilenameSafe(osDirname.c_str(), *papszIter, nullptr)
    1531             :                     .c_str());
    1532             :         }
    1533             :     }
    1534          17 :     CSLDestroy(papszFiles);
    1535             : 
    1536          17 :     return OGRERR_NONE;
    1537             : }
    1538             : 
    1539             : /************************************************************************/
    1540             : /*                             FlushCache()                             */
    1541             : /************************************************************************/
    1542             : 
    1543         873 : CPLErr OGROpenFileGDBDataSource::FlushCache(bool /*bAtClosing*/)
    1544             : {
    1545         873 :     if (eAccess != GA_Update)
    1546         562 :         return CE_None;
    1547             : 
    1548         311 :     CPLErr eErr = CE_None;
    1549         899 :     for (auto &poLayer : m_apoLayers)
    1550             :     {
    1551         588 :         if (poLayer->SyncToDisk() != OGRERR_NONE)
    1552           0 :             eErr = CE_Failure;
    1553             :     }
    1554         311 :     return eErr;
    1555             : }
    1556             : 
    1557             : /************************************************************************/
    1558             : /*                          AddFieldDomain()                            */
    1559             : /************************************************************************/
    1560             : 
    1561           6 : bool OGROpenFileGDBDataSource::AddFieldDomain(
    1562             :     std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
    1563             : {
    1564          12 :     const std::string domainName(domain->GetName());
    1565           6 :     if (eAccess != GA_Update)
    1566             :     {
    1567           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1568             :                  "AddFieldDomain() not supported on read-only dataset");
    1569           0 :         return false;
    1570             :     }
    1571             : 
    1572           6 :     if (GetFieldDomain(domainName) != nullptr)
    1573             :     {
    1574           0 :         failureReason = "A domain of identical name already exists";
    1575           0 :         return false;
    1576             :     }
    1577             : 
    1578           6 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    1579           0 :         return false;
    1580             : 
    1581             :     std::string osXML =
    1582          12 :         BuildXMLFieldDomainDef(domain.get(), false, failureReason);
    1583           6 :     if (osXML.empty())
    1584             :     {
    1585           0 :         return false;
    1586             :     }
    1587             : 
    1588          12 :     const std::string osThisGUID = OFGDBGenerateUUID();
    1589             : 
    1590          12 :     FileGDBTable oTable;
    1591           6 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
    1592           0 :         return false;
    1593             : 
    1594           6 :     FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
    1595           6 :     FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
    1596           6 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
    1597           6 :     FETCH_FIELD_IDX(iPhysicalName, "PhysicalName", FGFT_STRING);
    1598           6 :     FETCH_FIELD_IDX(iPath, "Path", FGFT_STRING);
    1599           6 :     FETCH_FIELD_IDX(iURL, "URL", FGFT_STRING);
    1600           6 :     FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
    1601           6 :     FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
    1602             : 
    1603           6 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
    1604          18 :                                  FileGDBField::UNSET_FIELD);
    1605           6 :     fields[iUUID].String = const_cast<char *>(osThisGUID.c_str());
    1606           6 :     switch (domain->GetDomainType())
    1607             :     {
    1608           4 :         case OFDT_CODED:
    1609           4 :             fields[iType].String = const_cast<char *>(pszCodedDomainTypeUUID);
    1610           4 :             break;
    1611             : 
    1612           2 :         case OFDT_RANGE:
    1613           2 :             fields[iType].String = const_cast<char *>(pszRangeDomainTypeUUID);
    1614           2 :             break;
    1615             : 
    1616           0 :         case OFDT_GLOB:
    1617           0 :             CPLAssert(false);
    1618             :             break;
    1619             :     }
    1620           6 :     fields[iName].String = const_cast<char *>(domainName.c_str());
    1621          12 :     CPLString osUCName(domainName);
    1622           6 :     osUCName.toupper();
    1623           6 :     fields[iPhysicalName].String = const_cast<char *>(osUCName.c_str());
    1624           6 :     fields[iPath].String = const_cast<char *>("");
    1625           6 :     fields[iURL].String = const_cast<char *>("");
    1626           6 :     fields[iDefinition].String = const_cast<char *>(osXML.c_str());
    1627           6 :     fields[iProperties].Integer = 1;
    1628             : 
    1629           6 :     if (!(oTable.CreateFeature(fields, nullptr) && oTable.Sync()))
    1630           0 :         return false;
    1631             : 
    1632           6 :     m_oMapFieldDomains[domainName] = std::move(domain);
    1633             : 
    1634           6 :     return true;
    1635             : }
    1636             : 
    1637             : /************************************************************************/
    1638             : /*                         DeleteFieldDomain()                          */
    1639             : /************************************************************************/
    1640             : 
    1641           2 : bool OGROpenFileGDBDataSource::DeleteFieldDomain(
    1642             :     const std::string &name, std::string & /*failureReason*/)
    1643             : {
    1644           2 :     if (eAccess != GA_Update)
    1645             :     {
    1646           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1647             :                  "DeleteFieldDomain() not supported on read-only dataset");
    1648           0 :         return false;
    1649             :     }
    1650             : 
    1651           2 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    1652           0 :         return false;
    1653             : 
    1654             :     // Remove object from GDB_Items
    1655           4 :     std::string osUUID;
    1656             :     {
    1657           2 :         FileGDBTable oTable;
    1658           2 :         if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
    1659           0 :             return false;
    1660             : 
    1661           2 :         FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
    1662           2 :         FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
    1663           2 :         FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
    1664             : 
    1665          14 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    1666             :              ++iCurFeat)
    1667             :         {
    1668          13 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    1669          13 :             if (iCurFeat < 0)
    1670           0 :                 break;
    1671          13 :             const auto psName = oTable.GetFieldValue(iName);
    1672          13 :             if (psName && psName->String == name)
    1673             :             {
    1674           1 :                 const auto psType = oTable.GetFieldValue(iType);
    1675           1 :                 if (psType && (EQUAL(psType->String, pszRangeDomainTypeUUID) ||
    1676           1 :                                EQUAL(psType->String, pszCodedDomainTypeUUID)))
    1677             :                 {
    1678           1 :                     const auto psUUID = oTable.GetFieldValue(iUUID);
    1679           1 :                     if (psUUID)
    1680             :                     {
    1681           1 :                         osUUID = psUUID->String;
    1682             :                     }
    1683             : 
    1684           1 :                     if (!(oTable.DeleteFeature(iCurFeat + 1) && oTable.Sync()))
    1685             :                     {
    1686           0 :                         return false;
    1687             :                     }
    1688           1 :                     break;
    1689             :                 }
    1690             :             }
    1691             :         }
    1692             :     }
    1693           2 :     if (osUUID.empty())
    1694           1 :         return false;
    1695             : 
    1696             :     // Remove links from layers to domain, into GDB_ItemRelationships
    1697             :     {
    1698           1 :         FileGDBTable oTable;
    1699           1 :         if (!oTable.Open(m_osGDBItemRelationshipsFilename.c_str(), true))
    1700           0 :             return false;
    1701             : 
    1702           1 :         FETCH_FIELD_IDX(iDestID, "DestID", FGFT_GUID);
    1703           1 :         FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
    1704             : 
    1705           5 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    1706             :              ++iCurFeat)
    1707             :         {
    1708           4 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    1709           4 :             if (iCurFeat < 0)
    1710           0 :                 break;
    1711             : 
    1712           4 :             const auto psType = oTable.GetFieldValue(iType);
    1713           4 :             if (psType && EQUAL(psType->String, pszDomainInDatasetUUID))
    1714             :             {
    1715           3 :                 const auto psDestID = oTable.GetFieldValue(iDestID);
    1716           3 :                 if (psDestID && EQUAL(psDestID->String, osUUID.c_str()))
    1717             :                 {
    1718           0 :                     if (!(oTable.DeleteFeature(iCurFeat + 1) && oTable.Sync()))
    1719             :                     {
    1720           0 :                         return false;
    1721             :                     }
    1722             :                 }
    1723             :             }
    1724             :         }
    1725             : 
    1726           1 :         if (!oTable.Sync())
    1727             :         {
    1728           0 :             return false;
    1729             :         }
    1730             :     }
    1731             : 
    1732           1 :     m_oMapFieldDomains.erase(name);
    1733             : 
    1734           1 :     return true;
    1735             : }
    1736             : 
    1737             : /************************************************************************/
    1738             : /*                        UpdateFieldDomain()                           */
    1739             : /************************************************************************/
    1740             : 
    1741           1 : bool OGROpenFileGDBDataSource::UpdateFieldDomain(
    1742             :     std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
    1743             : {
    1744           2 :     const std::string domainName(domain->GetName());
    1745           1 :     if (eAccess != GA_Update)
    1746             :     {
    1747           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1748             :                  "UpdateFieldDomain() not supported on read-only dataset");
    1749           0 :         return false;
    1750             :     }
    1751             : 
    1752           1 :     if (GetFieldDomain(domainName) == nullptr)
    1753             :     {
    1754           0 :         failureReason = "The domain should already exist to be updated";
    1755           0 :         return false;
    1756             :     }
    1757             : 
    1758           1 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    1759           0 :         return false;
    1760             : 
    1761             :     std::string osXML =
    1762           2 :         BuildXMLFieldDomainDef(domain.get(), false, failureReason);
    1763           1 :     if (osXML.empty())
    1764             :     {
    1765           0 :         return false;
    1766             :     }
    1767             : 
    1768           2 :     FileGDBTable oTable;
    1769           1 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
    1770           0 :         return false;
    1771             : 
    1772           1 :     FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
    1773           1 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
    1774           1 :     FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
    1775             : 
    1776           1 :     bool bMatchFound = false;
    1777           3 :     for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    1778             :          ++iCurFeat)
    1779             :     {
    1780           3 :         iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    1781           3 :         if (iCurFeat < 0)
    1782           0 :             break;
    1783           3 :         const auto psName = oTable.GetFieldValue(iName);
    1784           3 :         if (psName && psName->String == domainName)
    1785             :         {
    1786           1 :             const auto psType = oTable.GetFieldValue(iType);
    1787           1 :             if (psType && (EQUAL(psType->String, pszRangeDomainTypeUUID) ||
    1788           0 :                            EQUAL(psType->String, pszCodedDomainTypeUUID)))
    1789             :             {
    1790           1 :                 auto asFields = oTable.GetAllFieldValues();
    1791             : 
    1792           2 :                 if (!OGR_RawField_IsNull(&asFields[iDefinition]) &&
    1793           1 :                     !OGR_RawField_IsUnset(&asFields[iDefinition]))
    1794             :                 {
    1795           1 :                     CPLFree(asFields[iDefinition].String);
    1796             :                 }
    1797           1 :                 asFields[iDefinition].String = CPLStrdup(osXML.c_str());
    1798             : 
    1799           1 :                 const char *pszNewTypeUUID = "";
    1800           1 :                 CPL_IGNORE_RET_VAL(pszNewTypeUUID);  // Make CSA happy
    1801           1 :                 switch (domain->GetDomainType())
    1802             :                 {
    1803           0 :                     case OFDT_CODED:
    1804           0 :                         pszNewTypeUUID = pszCodedDomainTypeUUID;
    1805           0 :                         break;
    1806             : 
    1807           1 :                     case OFDT_RANGE:
    1808           1 :                         pszNewTypeUUID = pszRangeDomainTypeUUID;
    1809           1 :                         break;
    1810             : 
    1811           0 :                     case OFDT_GLOB:
    1812           0 :                         CPLAssert(false);
    1813             :                         break;
    1814             :                 }
    1815             : 
    1816           2 :                 if (!OGR_RawField_IsNull(&asFields[iType]) &&
    1817           1 :                     !OGR_RawField_IsUnset(&asFields[iType]))
    1818             :                 {
    1819           1 :                     CPLFree(asFields[iType].String);
    1820             :                 }
    1821           1 :                 asFields[iType].String = CPLStrdup(pszNewTypeUUID);
    1822             : 
    1823             :                 bool bRet =
    1824           1 :                     oTable.UpdateFeature(iCurFeat + 1, asFields, nullptr);
    1825           1 :                 oTable.FreeAllFieldValues(asFields);
    1826           1 :                 if (!bRet)
    1827           0 :                     return false;
    1828           1 :                 bMatchFound = true;
    1829           1 :                 break;
    1830             :             }
    1831             :         }
    1832             : 
    1833           2 :         if (!oTable.Sync())
    1834             :         {
    1835           0 :             return false;
    1836             :         }
    1837             :     }
    1838             : 
    1839           1 :     if (!bMatchFound)
    1840           0 :         return false;
    1841             : 
    1842           1 :     m_oMapFieldDomains[domainName] = std::move(domain);
    1843             : 
    1844           1 :     return true;
    1845             : }
    1846             : 
    1847             : /************************************************************************/
    1848             : /*                        GetRelationshipNames()                        */
    1849             : /************************************************************************/
    1850             : 
    1851          24 : std::vector<std::string> OGROpenFileGDBDataSource::GetRelationshipNames(
    1852             :     CPL_UNUSED CSLConstList papszOptions) const
    1853             : 
    1854             : {
    1855          24 :     std::vector<std::string> oasNames;
    1856          24 :     oasNames.reserve(m_osMapRelationships.size());
    1857         105 :     for (auto it = m_osMapRelationships.begin();
    1858         186 :          it != m_osMapRelationships.end(); ++it)
    1859             :     {
    1860          81 :         oasNames.emplace_back(it->first);
    1861             :     }
    1862          24 :     return oasNames;
    1863             : }
    1864             : 
    1865             : /************************************************************************/
    1866             : /*                        GetRelationship()                             */
    1867             : /************************************************************************/
    1868             : 
    1869             : const GDALRelationship *
    1870          79 : OGROpenFileGDBDataSource::GetRelationship(const std::string &name) const
    1871             : 
    1872             : {
    1873          79 :     auto it = m_osMapRelationships.find(name);
    1874          79 :     if (it == m_osMapRelationships.end())
    1875          29 :         return nullptr;
    1876             : 
    1877          50 :     return it->second.get();
    1878             : }
    1879             : 
    1880             : /************************************************************************/
    1881             : /*                          AddRelationship()                           */
    1882             : /************************************************************************/
    1883             : 
    1884          17 : bool OGROpenFileGDBDataSource::AddRelationship(
    1885             :     std::unique_ptr<GDALRelationship> &&relationship,
    1886             :     std::string &failureReason)
    1887             : {
    1888          17 :     if (FlushCache(false) != CE_None)
    1889           0 :         return false;
    1890             : 
    1891          34 :     const std::string relationshipName(relationship->GetName());
    1892          17 :     if (eAccess != GA_Update)
    1893             :     {
    1894           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1895             :                  "AddRelationship() not supported on read-only dataset");
    1896           0 :         return false;
    1897             :     }
    1898             : 
    1899          17 :     if (GetRelationship(relationshipName) != nullptr)
    1900             :     {
    1901           1 :         failureReason = "A relationship of identical name already exists";
    1902           1 :         return false;
    1903             :     }
    1904             : 
    1905          16 :     if (relationship->GetCardinality() ==
    1906             :         GDALRelationshipCardinality::GRC_MANY_TO_ONE)
    1907             :     {
    1908           0 :         failureReason = "Many to one relationships are not supported";
    1909           0 :         return false;
    1910             :     }
    1911          16 :     else if (relationship->GetCardinality() ==
    1912           5 :                  GDALRelationshipCardinality::GRC_MANY_TO_MANY &&
    1913          20 :              !relationship->GetMappingTableName().empty() &&
    1914           4 :              relationship->GetName() != relationship->GetMappingTableName())
    1915             :     {
    1916             :         failureReason = "Mapping table name must match relationship name for "
    1917           1 :                         "many-to-many relationships";
    1918           1 :         return false;
    1919             :     }
    1920             : 
    1921          15 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    1922           0 :         return false;
    1923             : 
    1924          30 :     const std::string osThisGUID = OFGDBGenerateUUID();
    1925             : 
    1926          30 :     FileGDBTable oTable;
    1927          30 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true) ||
    1928          15 :         oTable.GetTotalRecordCount() >= INT32_MAX)
    1929           0 :         return false;
    1930             : 
    1931             :     // hopefully this just needs to be a unique value. Seems to autoincrement
    1932             :     // when created from ArcMap at least!
    1933          15 :     const int iDsId = static_cast<int>(oTable.GetTotalRecordCount() + 1);
    1934             : 
    1935          30 :     std::string osMappingTableOidName;
    1936          15 :     if (relationship->GetCardinality() ==
    1937             :         GDALRelationshipCardinality::GRC_MANY_TO_MANY)
    1938             :     {
    1939           4 :         if (!relationship->GetMappingTableName().empty())
    1940             :         {
    1941             :             auto poLayer =
    1942           3 :                 GetLayerByName(relationship->GetMappingTableName().c_str());
    1943           3 :             if (poLayer)
    1944             :             {
    1945           3 :                 osMappingTableOidName = poLayer->GetFIDColumn();
    1946             :             }
    1947             :         }
    1948             :         else
    1949             :         {
    1950             :             // auto create mapping table
    1951           1 :             CPLStringList aosOptions;
    1952           1 :             aosOptions.SetNameValue("FID", "RID");
    1953           1 :             OGRLayer *poMappingTable = ICreateLayer(
    1954           1 :                 relationship->GetName().c_str(), nullptr, aosOptions.List());
    1955           1 :             if (!poMappingTable)
    1956             :             {
    1957           0 :                 failureReason = "Could not create mapping table " +
    1958           0 :                                 relationship->GetMappingTableName();
    1959           0 :                 return false;
    1960             :             }
    1961             : 
    1962           1 :             OGRFieldDefn oOriginFkFieldDefn("origin_fk", OFTString);
    1963           1 :             if (poMappingTable->CreateField(&oOriginFkFieldDefn) != OGRERR_NONE)
    1964             :             {
    1965             :                 failureReason =
    1966           0 :                     "Could not create origin_fk field in mapping table " +
    1967           0 :                     relationship->GetMappingTableName();
    1968           0 :                 return false;
    1969             :             }
    1970             : 
    1971           1 :             OGRFieldDefn oDestinationFkFieldDefn("destination_fk", OFTString);
    1972           1 :             if (poMappingTable->CreateField(&oDestinationFkFieldDefn) !=
    1973             :                 OGRERR_NONE)
    1974             :             {
    1975             :                 failureReason =
    1976           0 :                     "Could not create destination_fk field in mapping table " +
    1977           0 :                     relationship->GetMappingTableName();
    1978           0 :                 return false;
    1979             :             }
    1980             : 
    1981           1 :             osMappingTableOidName = "RID";
    1982           1 :             relationship->SetMappingTableName(relationship->GetName());
    1983           2 :             relationship->SetLeftMappingTableFields({"origin_fk"});
    1984           2 :             relationship->SetRightMappingTableFields({"destination_fk"});
    1985             :         }
    1986             :     }
    1987             : 
    1988             :     std::string osXML = BuildXMLRelationshipDef(
    1989          30 :         relationship.get(), iDsId, osMappingTableOidName, failureReason);
    1990          15 :     if (osXML.empty())
    1991             :     {
    1992           0 :         return false;
    1993             :     }
    1994             : 
    1995             :     std::string osItemInfoXML =
    1996          30 :         BuildXMLRelationshipItemInfo(relationship.get(), failureReason);
    1997          15 :     if (osItemInfoXML.empty())
    1998             :     {
    1999           0 :         return false;
    2000             :     }
    2001             : 
    2002             :     std::string osDocumentationXML =
    2003          30 :         BuildXMLRelationshipDocumentation(relationship.get(), failureReason);
    2004          15 :     if (osDocumentationXML.empty())
    2005             :     {
    2006           0 :         return false;
    2007             :     }
    2008             : 
    2009          30 :     std::string osOriginUUID;
    2010          15 :     if (!FindUUIDFromName(relationship->GetLeftTableName(), osOriginUUID))
    2011             :     {
    2012           2 :         failureReason = ("Left table " + relationship->GetLeftTableName() +
    2013             :                          " is not an existing layer in the dataset")
    2014           1 :                             .c_str();
    2015           1 :         return false;
    2016             :     }
    2017          28 :     std::string osDestinationUUID;
    2018          14 :     if (!FindUUIDFromName(relationship->GetRightTableName(), osDestinationUUID))
    2019             :     {
    2020           0 :         failureReason = ("Right table " + relationship->GetRightTableName() +
    2021             :                          " is not an existing layer in the dataset")
    2022           0 :                             .c_str();
    2023           0 :         return false;
    2024             :     }
    2025             : 
    2026          14 :     FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
    2027          14 :     FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
    2028          14 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
    2029          14 :     FETCH_FIELD_IDX(iPhysicalName, "PhysicalName", FGFT_STRING);
    2030          14 :     FETCH_FIELD_IDX(iPath, "Path", FGFT_STRING);
    2031          14 :     FETCH_FIELD_IDX(iDatasetSubtype1, "DatasetSubtype1", FGFT_INT32);
    2032          14 :     FETCH_FIELD_IDX(iDatasetSubtype2, "DatasetSubtype2", FGFT_INT32);
    2033          14 :     FETCH_FIELD_IDX(iURL, "URL", FGFT_STRING);
    2034          14 :     FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
    2035          14 :     FETCH_FIELD_IDX(iDocumentation, "Documentation", FGFT_XML);
    2036          14 :     FETCH_FIELD_IDX(iItemInfo, "ItemInfo", FGFT_XML);
    2037          14 :     FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
    2038             : 
    2039          14 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
    2040          42 :                                  FileGDBField::UNSET_FIELD);
    2041          14 :     fields[iUUID].String = const_cast<char *>(osThisGUID.c_str());
    2042          14 :     fields[iType].String = const_cast<char *>(pszRelationshipTypeUUID);
    2043          14 :     fields[iName].String = const_cast<char *>(relationshipName.c_str());
    2044          28 :     CPLString osUCName(relationshipName);
    2045          14 :     osUCName.toupper();
    2046          14 :     fields[iPhysicalName].String = const_cast<char *>(osUCName.c_str());
    2047          28 :     const std::string osPath = "\\" + relationshipName;
    2048          14 :     fields[iPath].String = const_cast<char *>(osPath.c_str());
    2049          14 :     switch (relationship->GetCardinality())
    2050             :     {
    2051           7 :         case GDALRelationshipCardinality::GRC_ONE_TO_ONE:
    2052           7 :             fields[iDatasetSubtype1].Integer = 1;
    2053           7 :             break;
    2054           3 :         case GDALRelationshipCardinality::GRC_ONE_TO_MANY:
    2055           3 :             fields[iDatasetSubtype1].Integer = 2;
    2056           3 :             break;
    2057           4 :         case GDALRelationshipCardinality::GRC_MANY_TO_MANY:
    2058           4 :             fields[iDatasetSubtype1].Integer = 3;
    2059           4 :             break;
    2060           0 :         case GDALRelationshipCardinality::GRC_MANY_TO_ONE:
    2061             :             // unreachable
    2062           0 :             break;
    2063             :     }
    2064          14 :     fields[iDatasetSubtype2].Integer = 0;
    2065          14 :     fields[iURL].String = const_cast<char *>("");
    2066          14 :     fields[iDefinition].String = const_cast<char *>(osXML.c_str());
    2067          14 :     fields[iDocumentation].String =
    2068          14 :         const_cast<char *>(osDocumentationXML.c_str());
    2069          14 :     fields[iItemInfo].String = const_cast<char *>(osItemInfoXML.c_str());
    2070          14 :     fields[iProperties].Integer = 1;
    2071             : 
    2072          14 :     if (!(oTable.CreateFeature(fields, nullptr) && oTable.Sync()))
    2073           0 :         return false;
    2074             : 
    2075          14 :     if (!RegisterRelationshipInItemRelationships(osThisGUID, osOriginUUID,
    2076             :                                                  osDestinationUUID))
    2077           0 :         return false;
    2078             : 
    2079          14 :     m_osMapRelationships[relationshipName] = std::move(relationship);
    2080             : 
    2081          14 :     return true;
    2082             : }
    2083             : 
    2084             : /************************************************************************/
    2085             : /*                         DeleteRelationship()                         */
    2086             : /************************************************************************/
    2087             : 
    2088           2 : bool OGROpenFileGDBDataSource::DeleteRelationship(const std::string &name,
    2089             :                                                   std::string &failureReason)
    2090             : {
    2091           2 :     if (eAccess != GA_Update)
    2092             :     {
    2093           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2094             :                  "DeleteRelationship() not supported on read-only dataset");
    2095           0 :         return false;
    2096             :     }
    2097             : 
    2098           2 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    2099           0 :         return false;
    2100             : 
    2101             :     // Remove from GDB_Items
    2102           4 :     std::string osUUID;
    2103             :     {
    2104           2 :         FileGDBTable oTable;
    2105           2 :         if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
    2106           0 :             return false;
    2107             : 
    2108           2 :         FETCH_FIELD_IDX_WITH_RET(iUUID, "UUID", FGFT_GLOBALID, false);
    2109           2 :         FETCH_FIELD_IDX_WITH_RET(iType, "Type", FGFT_GUID, false);
    2110           2 :         FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, false);
    2111             : 
    2112          36 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    2113             :              ++iCurFeat)
    2114             :         {
    2115          34 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    2116          34 :             if (iCurFeat < 0)
    2117           0 :                 break;
    2118             : 
    2119          34 :             const auto psType = oTable.GetFieldValue(iType);
    2120          34 :             if (!psType || !EQUAL(psType->String, pszRelationshipTypeUUID))
    2121             :             {
    2122          26 :                 continue;
    2123             :             }
    2124             : 
    2125           8 :             const auto psName = oTable.GetFieldValue(iName);
    2126           8 :             if (psName && strcmp(psName->String, name.c_str()) != 0)
    2127             :             {
    2128           7 :                 continue;
    2129             :             }
    2130             : 
    2131           1 :             const auto psUUID = oTable.GetFieldValue(iUUID);
    2132           1 :             if (psUUID)
    2133             :             {
    2134           1 :                 osUUID = psUUID->String;
    2135           1 :                 if (!(oTable.DeleteFeature(iCurFeat + 1) && oTable.Sync()))
    2136             :                 {
    2137             :                     failureReason =
    2138           0 :                         "Could not delete relationship from GDB_Items table";
    2139           0 :                     return false;
    2140             :                 }
    2141             :             }
    2142             :         }
    2143             :     }
    2144             : 
    2145           2 :     if (osUUID.empty())
    2146             :     {
    2147           1 :         failureReason = "Could not find relationship with name " + name;
    2148           1 :         return false;
    2149             :     }
    2150             : 
    2151           1 :     if (!RemoveRelationshipFromItemRelationships(osUUID))
    2152             :     {
    2153             :         failureReason =
    2154           0 :             "Could not remove relationship from GDB_ItemRelationships";
    2155           0 :         return false;
    2156             :     }
    2157             : 
    2158           1 :     m_osMapRelationships.erase(name);
    2159           1 :     return true;
    2160             : }
    2161             : 
    2162             : /************************************************************************/
    2163             : /*                        UpdateRelationship()                          */
    2164             : /************************************************************************/
    2165             : 
    2166           3 : bool OGROpenFileGDBDataSource::UpdateRelationship(
    2167             :     std::unique_ptr<GDALRelationship> &&relationship,
    2168             :     std::string &failureReason)
    2169             : {
    2170           6 :     const std::string relationshipName(relationship->GetName());
    2171           3 :     if (eAccess != GA_Update)
    2172             :     {
    2173           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2174             :                  "UpdateRelationship() not supported on read-only dataset");
    2175           0 :         return false;
    2176             :     }
    2177             : 
    2178           3 :     if (GetRelationship(relationshipName) == nullptr)
    2179             :     {
    2180           1 :         failureReason = "The relationship should already exist to be updated";
    2181           1 :         return false;
    2182             :     }
    2183             : 
    2184           2 :     if (relationship->GetCardinality() ==
    2185             :         GDALRelationshipCardinality::GRC_MANY_TO_ONE)
    2186             :     {
    2187           0 :         failureReason = "Many to one relationships are not supported";
    2188           0 :         return false;
    2189             :     }
    2190             : 
    2191           2 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    2192           0 :         return false;
    2193             : 
    2194           4 :     std::string osOriginUUID;
    2195           2 :     if (!FindUUIDFromName(relationship->GetLeftTableName(), osOriginUUID))
    2196             :     {
    2197           0 :         failureReason = ("Left table " + relationship->GetLeftTableName() +
    2198             :                          " is not an existing layer in the dataset")
    2199           0 :                             .c_str();
    2200           0 :         return false;
    2201             :     }
    2202           4 :     std::string osDestinationUUID;
    2203           2 :     if (!FindUUIDFromName(relationship->GetRightTableName(), osDestinationUUID))
    2204             :     {
    2205           0 :         failureReason = ("Right table " + relationship->GetRightTableName() +
    2206             :                          " is not an existing layer in the dataset")
    2207           0 :                             .c_str();
    2208           0 :         return false;
    2209             :     }
    2210             : 
    2211           4 :     FileGDBTable oTable;
    2212           4 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true) ||
    2213           2 :         oTable.GetTotalRecordCount() >= INT32_MAX)
    2214             :     {
    2215           0 :         return false;
    2216             :     }
    2217             : 
    2218             :     // hopefully this just needs to be a unique value. Seems to autoincrement
    2219             :     // when created from ArcMap at least!
    2220           2 :     const int iDsId = static_cast<int>(oTable.GetTotalRecordCount()) + 1;
    2221             : 
    2222           4 :     std::string osMappingTableOidName;
    2223           2 :     if (relationship->GetCardinality() ==
    2224             :         GDALRelationshipCardinality::GRC_MANY_TO_MANY)
    2225             :     {
    2226           0 :         if (!relationship->GetMappingTableName().empty())
    2227             :         {
    2228             :             auto poLayer =
    2229           0 :                 GetLayerByName(relationship->GetMappingTableName().c_str());
    2230           0 :             if (poLayer)
    2231             :             {
    2232           0 :                 osMappingTableOidName = poLayer->GetFIDColumn();
    2233             :             }
    2234             :         }
    2235           0 :         if (osMappingTableOidName.empty())
    2236             :         {
    2237           0 :             failureReason = "Relationship mapping table does not exist";
    2238           0 :             return false;
    2239             :         }
    2240             :     }
    2241             : 
    2242             :     std::string osXML = BuildXMLRelationshipDef(
    2243           4 :         relationship.get(), iDsId, osMappingTableOidName, failureReason);
    2244           2 :     if (osXML.empty())
    2245             :     {
    2246           0 :         return false;
    2247             :     }
    2248             : 
    2249           2 :     FETCH_FIELD_IDX_WITH_RET(iUUID, "UUID", FGFT_GLOBALID, false);
    2250           2 :     FETCH_FIELD_IDX_WITH_RET(iType, "Type", FGFT_GUID, false);
    2251           2 :     FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, false);
    2252           2 :     FETCH_FIELD_IDX_WITH_RET(iDefinition, "Definition", FGFT_XML, false);
    2253           2 :     FETCH_FIELD_IDX_WITH_RET(iDatasetSubtype1, "DatasetSubtype1", FGFT_INT32,
    2254             :                              false);
    2255             : 
    2256           2 :     bool bMatchFound = false;
    2257           4 :     std::string osUUID;
    2258          16 :     for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    2259             :          ++iCurFeat)
    2260             :     {
    2261          16 :         iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    2262          16 :         if (iCurFeat < 0)
    2263           0 :             break;
    2264          16 :         const auto psName = oTable.GetFieldValue(iName);
    2265          16 :         if (psName && psName->String == relationshipName)
    2266             :         {
    2267           2 :             const auto psType = oTable.GetFieldValue(iType);
    2268           2 :             if (psType && EQUAL(psType->String, pszRelationshipTypeUUID))
    2269             :             {
    2270           2 :                 const auto psUUID = oTable.GetFieldValue(iUUID);
    2271           2 :                 if (psUUID)
    2272             :                 {
    2273           2 :                     osUUID = psUUID->String;
    2274             :                 }
    2275             : 
    2276           2 :                 auto asFields = oTable.GetAllFieldValues();
    2277             : 
    2278           4 :                 if (!OGR_RawField_IsNull(&asFields[iDefinition]) &&
    2279           2 :                     !OGR_RawField_IsUnset(&asFields[iDefinition]))
    2280             :                 {
    2281           2 :                     CPLFree(asFields[iDefinition].String);
    2282             :                 }
    2283           2 :                 asFields[iDefinition].String = CPLStrdup(osXML.c_str());
    2284             : 
    2285           2 :                 switch (relationship->GetCardinality())
    2286             :                 {
    2287           0 :                     case GDALRelationshipCardinality::GRC_ONE_TO_ONE:
    2288           0 :                         asFields[iDatasetSubtype1].Integer = 1;
    2289           0 :                         break;
    2290           2 :                     case GDALRelationshipCardinality::GRC_ONE_TO_MANY:
    2291           2 :                         asFields[iDatasetSubtype1].Integer = 2;
    2292           2 :                         break;
    2293           0 :                     case GDALRelationshipCardinality::GRC_MANY_TO_MANY:
    2294           0 :                         asFields[iDatasetSubtype1].Integer = 3;
    2295           0 :                         break;
    2296           0 :                     case GDALRelationshipCardinality::GRC_MANY_TO_ONE:
    2297             :                         // unreachable
    2298           0 :                         break;
    2299             :                 }
    2300             : 
    2301             :                 bool bRet =
    2302           2 :                     oTable.UpdateFeature(iCurFeat + 1, asFields, nullptr);
    2303           2 :                 oTable.FreeAllFieldValues(asFields);
    2304           2 :                 if (!bRet)
    2305           0 :                     return false;
    2306           2 :                 bMatchFound = true;
    2307           2 :                 break;
    2308             :             }
    2309             :         }
    2310             : 
    2311          14 :         if (!oTable.Sync())
    2312             :         {
    2313           0 :             return false;
    2314             :         }
    2315             :     }
    2316             : 
    2317           2 :     if (!bMatchFound)
    2318           0 :         return false;
    2319             : 
    2320             :     // First delete all existing item relationships for the item, and then we'll
    2321             :     // rebuild them again.
    2322           2 :     if (!RemoveRelationshipFromItemRelationships(osUUID))
    2323             :     {
    2324             :         failureReason =
    2325           0 :             "Could not remove relationship from GDB_ItemRelationships";
    2326           0 :         return false;
    2327             :     }
    2328           2 :     if (!RegisterRelationshipInItemRelationships(osUUID, osOriginUUID,
    2329             :                                                  osDestinationUUID))
    2330             :     {
    2331             :         failureReason =
    2332           0 :             "Could not register relationship in GDB_ItemRelationships";
    2333           0 :         return false;
    2334             :     }
    2335             : 
    2336           2 :     m_osMapRelationships[relationshipName] = std::move(relationship);
    2337             : 
    2338           2 :     return true;
    2339             : }
    2340             : 
    2341             : /************************************************************************/
    2342             : /*                        StartTransaction()                            */
    2343             : /************************************************************************/
    2344             : 
    2345          16 : OGRErr OGROpenFileGDBDataSource::StartTransaction(int bForce)
    2346             : {
    2347          16 :     if (!bForce)
    2348             :     {
    2349           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2350             :                  "Transactions only supported in forced mode");
    2351           0 :         return OGRERR_UNSUPPORTED_OPERATION;
    2352             :     }
    2353             : 
    2354          16 :     if (eAccess != GA_Update)
    2355           1 :         return OGRERR_FAILURE;
    2356             : 
    2357          15 :     if (m_bInTransaction)
    2358             :     {
    2359           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    2360             :                  "Transaction is already in progress");
    2361           1 :         return OGRERR_FAILURE;
    2362             :     }
    2363             : 
    2364          28 :     m_osTransactionBackupDirname = CPLFormFilenameSafe(
    2365          14 :         m_osDirName.c_str(), ".ogrtransaction_backup", nullptr);
    2366             :     VSIStatBufL sStat;
    2367          14 :     if (VSIStatL(m_osTransactionBackupDirname.c_str(), &sStat) == 0)
    2368             :     {
    2369           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    2370             :                  "A previous backup directory %s already exists, which means "
    2371             :                  "that a previous transaction was not cleanly committed or "
    2372             :                  "rolled back.\n"
    2373             :                  "Either manually restore the previous state from that "
    2374             :                  "directory or remove it, before creating a new transaction.",
    2375             :                  m_osTransactionBackupDirname.c_str());
    2376           1 :         return OGRERR_FAILURE;
    2377             :     }
    2378          13 :     else if (VSIMkdir(m_osTransactionBackupDirname.c_str(), 0755) != 0)
    2379             :     {
    2380           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot create directory %s",
    2381             :                  m_osTransactionBackupDirname.c_str());
    2382           0 :         return OGRERR_FAILURE;
    2383             :     }
    2384             : 
    2385          13 :     m_bInTransaction = true;
    2386          13 :     return OGRERR_NONE;
    2387             : }
    2388             : 
    2389             : /************************************************************************/
    2390             : /*                   BackupSystemTablesForTransaction()                 */
    2391             : /************************************************************************/
    2392             : 
    2393          10 : bool OGROpenFileGDBDataSource::BackupSystemTablesForTransaction()
    2394             : {
    2395          10 :     if (m_bSystemTablesBackedup)
    2396           5 :         return true;
    2397             : 
    2398           5 :     char **papszFiles = VSIReadDir(m_osDirName.c_str());
    2399         106 :     for (char **papszIter = papszFiles;
    2400         106 :          papszIter != nullptr && *papszIter != nullptr; ++papszIter)
    2401             :     {
    2402         101 :         const std::string osBasename = CPLGetBasenameSafe(*papszIter);
    2403         101 :         if (osBasename.size() == strlen("a00000001") &&
    2404         179 :             osBasename.compare(0, 8, "a0000000") == 0 && osBasename[8] >= '1' &&
    2405          78 :             osBasename[8] <= '8')
    2406             :         {
    2407             :             const std::string osDestFilename = CPLFormFilenameSafe(
    2408          72 :                 m_osTransactionBackupDirname.c_str(), *papszIter, nullptr);
    2409             :             const std::string osSourceFilename =
    2410          72 :                 CPLFormFilenameSafe(m_osDirName.c_str(), *papszIter, nullptr);
    2411          72 :             if (CPLCopyFile(osDestFilename.c_str(), osSourceFilename.c_str()) !=
    2412             :                 0)
    2413             :             {
    2414           0 :                 CSLDestroy(papszFiles);
    2415           0 :                 return false;
    2416             :             }
    2417             :         }
    2418             :     }
    2419             : 
    2420           5 :     CSLDestroy(papszFiles);
    2421           5 :     m_bSystemTablesBackedup = true;
    2422           5 :     return true;
    2423             : }
    2424             : 
    2425             : /************************************************************************/
    2426             : /*                        CommitTransaction()                           */
    2427             : /************************************************************************/
    2428             : 
    2429           5 : OGRErr OGROpenFileGDBDataSource::CommitTransaction()
    2430             : {
    2431           5 :     if (!m_bInTransaction)
    2432             :     {
    2433           1 :         CPLError(CE_Failure, CPLE_AppDefined, "No transaction in progress");
    2434           1 :         return OGRERR_FAILURE;
    2435             :     }
    2436             : 
    2437           7 :     for (auto &poLayer : m_apoLayers)
    2438           3 :         poLayer->CommitEmulatedTransaction();
    2439             : 
    2440           4 :     VSIRmdirRecursive(m_osTransactionBackupDirname.c_str());
    2441             : 
    2442           4 :     m_bInTransaction = false;
    2443           4 :     m_bSystemTablesBackedup = false;
    2444           4 :     m_oSetLayersCreatedInTransaction.clear();
    2445           4 :     m_oSetLayersDeletedInTransaction.clear();
    2446             : 
    2447           4 :     return OGRERR_NONE;
    2448             : }
    2449             : 
    2450             : /************************************************************************/
    2451             : /*                       RollbackTransaction()                          */
    2452             : /************************************************************************/
    2453             : 
    2454          11 : OGRErr OGROpenFileGDBDataSource::RollbackTransaction()
    2455             : {
    2456          11 :     if (!m_bInTransaction)
    2457             :     {
    2458           1 :         CPLError(CE_Failure, CPLE_AppDefined, "No transaction in progress");
    2459           1 :         return OGRERR_FAILURE;
    2460             :     }
    2461             : 
    2462          10 :     OGRErr eErr = OGRERR_NONE;
    2463             : 
    2464             :     // Restore system tables
    2465             :     {
    2466          10 :         char **papszFiles = VSIReadDir(m_osTransactionBackupDirname.c_str());
    2467          10 :         if (papszFiles == nullptr)
    2468             :         {
    2469           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    2470             :                      "Backup directory %s no longer found! Original database "
    2471             :                      "cannot be restored",
    2472             :                      m_osTransactionBackupDirname.c_str());
    2473           2 :             return OGRERR_FAILURE;
    2474             :         }
    2475          78 :         for (char **papszIter = papszFiles;
    2476          78 :              papszIter != nullptr && *papszIter != nullptr; ++papszIter)
    2477             :         {
    2478         140 :             const std::string osBasename = CPLGetBasenameSafe(*papszIter);
    2479          70 :             if (osBasename.size() == strlen("a00000001") &&
    2480          56 :                 osBasename.compare(0, 8, "a0000000") == 0 &&
    2481         126 :                 osBasename[8] >= '1' && osBasename[8] <= '8')
    2482             :             {
    2483             :                 const std::string osDestFilename = CPLFormFilenameSafe(
    2484          88 :                     m_osDirName.c_str(), *papszIter, nullptr);
    2485             :                 const std::string osSourceFilename = CPLFormFilenameSafe(
    2486          88 :                     m_osTransactionBackupDirname.c_str(), *papszIter, nullptr);
    2487          44 :                 if (CPLCopyFile(osDestFilename.c_str(),
    2488          44 :                                 osSourceFilename.c_str()) != 0)
    2489             :                 {
    2490           0 :                     eErr = OGRERR_FAILURE;
    2491             :                 }
    2492             :             }
    2493             :         }
    2494           8 :         CSLDestroy(papszFiles);
    2495             :     }
    2496             : 
    2497             :     // Restore layers in their original state
    2498          13 :     for (auto &poLayer : m_apoLayers)
    2499           5 :         poLayer->RollbackEmulatedTransaction();
    2500           9 :     for (auto &poLayer : m_oSetLayersDeletedInTransaction)
    2501           1 :         poLayer->RollbackEmulatedTransaction();
    2502             : 
    2503             :     // Remove layers created during transaction
    2504           9 :     for (auto poLayer : m_oSetLayersCreatedInTransaction)
    2505             :     {
    2506             :         const std::string osThisBasename =
    2507           2 :             CPLGetBasenameSafe(poLayer->GetFilename().c_str());
    2508           1 :         poLayer->Close();
    2509             : 
    2510           1 :         char **papszFiles = VSIReadDir(m_osDirName.c_str());
    2511          23 :         for (char **papszIter = papszFiles;
    2512          23 :              papszIter != nullptr && *papszIter != nullptr; ++papszIter)
    2513             :         {
    2514          44 :             const std::string osBasename = CPLGetBasenameSafe(*papszIter);
    2515          22 :             if (osBasename == osThisBasename)
    2516             :             {
    2517             :                 const std::string osDestFilename = CPLFormFilenameSafe(
    2518           6 :                     m_osDirName.c_str(), *papszIter, nullptr);
    2519           3 :                 VSIUnlink(osDestFilename.c_str());
    2520             :             }
    2521             :         }
    2522           1 :         CSLDestroy(papszFiles);
    2523             :     }
    2524             : 
    2525           8 :     if (eErr == OGRERR_NONE)
    2526             :     {
    2527           8 :         if (VSIRmdirRecursive(m_osTransactionBackupDirname.c_str()) != 0)
    2528             :         {
    2529           0 :             CPLError(
    2530             :                 CE_Warning, CPLE_AppDefined,
    2531             :                 "Backup directory %s could not be destroyed. But original "
    2532             :                 "dataset "
    2533             :                 "should have been properly restored. You will need to manually "
    2534             :                 "remove the backup directory.",
    2535             :                 m_osTransactionBackupDirname.c_str());
    2536             :         }
    2537             :     }
    2538             :     else
    2539             :     {
    2540           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2541             :                  "Backup directory %s could not be properly restored onto "
    2542             :                  "live database. Corruption is likely!",
    2543             :                  m_osTransactionBackupDirname.c_str());
    2544             :     }
    2545             : 
    2546           8 :     m_bInTransaction = false;
    2547           8 :     m_bSystemTablesBackedup = false;
    2548           8 :     m_oSetLayersCreatedInTransaction.clear();
    2549           8 :     m_oSetLayersDeletedInTransaction.clear();
    2550             : 
    2551           8 :     return eErr;
    2552             : }

Generated by: LCOV version 1.14