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: 1047 1331 78.7 %
Date: 2024-11-21 22:18:42 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         194 : 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         388 :     FileGDBTable oTable;
      49         194 :     if (!oTable.Open(m_osGDBSpatialRefsFilename.c_str(), false))
      50           0 :         return false;
      51             : 
      52         194 :     FETCH_FIELD_IDX(iSRTEXT, "SRTEXT", FGFT_STRING);
      53         194 :     FETCH_FIELD_IDX(iFalseX, "FalseX", FGFT_FLOAT64);
      54         194 :     FETCH_FIELD_IDX(iFalseY, "FalseY", FGFT_FLOAT64);
      55         194 :     FETCH_FIELD_IDX(iXYUnits, "XYUnits", FGFT_FLOAT64);
      56         194 :     FETCH_FIELD_IDX(iFalseZ, "FalseZ", FGFT_FLOAT64);
      57         194 :     FETCH_FIELD_IDX(iZUnits, "ZUnits", FGFT_FLOAT64);
      58         194 :     FETCH_FIELD_IDX(iFalseM, "FalseM", FGFT_FLOAT64);
      59         194 :     FETCH_FIELD_IDX(iMUnits, "MUnits", FGFT_FLOAT64);
      60         194 :     FETCH_FIELD_IDX(iXYTolerance, "XYTolerance", FGFT_FLOAT64);
      61         194 :     FETCH_FIELD_IDX(iZTolerance, "ZTolerance", FGFT_FLOAT64);
      62         194 :     FETCH_FIELD_IDX(iMTolerance, "MTolerance", FGFT_FLOAT64);
      63             : 
      64         194 :     int64_t iCurFeat = 0;
      65         389 :     while (iCurFeat < oTable.GetTotalRecordCount())
      66             :     {
      67         219 :         iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
      68         219 :         if (iCurFeat < 0)
      69           0 :             break;
      70         219 :         iCurFeat++;
      71         219 :         const auto psSRTEXT = oTable.GetFieldValue(iSRTEXT);
      72         219 :         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         170 :     return false;
      95             : }
      96             : 
      97             : /***********************************************************************/
      98             : /*                       AddNewSpatialRef()                            */
      99             : /***********************************************************************/
     100             : 
     101         398 : 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         796 :     FileGDBTable oTable;
     108         398 :     if (!oTable.Open(m_osGDBSpatialRefsFilename.c_str(), true))
     109           0 :         return false;
     110             : 
     111         398 :     FETCH_FIELD_IDX(iSRTEXT, "SRTEXT", FGFT_STRING);
     112         398 :     FETCH_FIELD_IDX(iFalseX, "FalseX", FGFT_FLOAT64);
     113         398 :     FETCH_FIELD_IDX(iFalseY, "FalseY", FGFT_FLOAT64);
     114         398 :     FETCH_FIELD_IDX(iXYUnits, "XYUnits", FGFT_FLOAT64);
     115         398 :     FETCH_FIELD_IDX(iFalseZ, "FalseZ", FGFT_FLOAT64);
     116         398 :     FETCH_FIELD_IDX(iZUnits, "ZUnits", FGFT_FLOAT64);
     117         398 :     FETCH_FIELD_IDX(iFalseM, "FalseM", FGFT_FLOAT64);
     118         398 :     FETCH_FIELD_IDX(iMUnits, "MUnits", FGFT_FLOAT64);
     119         398 :     FETCH_FIELD_IDX(iXYTolerance, "XYTolerance", FGFT_FLOAT64);
     120         398 :     FETCH_FIELD_IDX(iZTolerance, "ZTolerance", FGFT_FLOAT64);
     121         398 :     FETCH_FIELD_IDX(iMTolerance, "MTolerance", FGFT_FLOAT64);
     122             : 
     123         398 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     124         796 :                                  FileGDBField::UNSET_FIELD);
     125         398 :     fields[iSRTEXT].String = const_cast<char *>(osWKT.c_str());
     126         398 :     fields[iFalseX].Real = dfXOrigin;
     127         398 :     fields[iFalseY].Real = dfYOrigin;
     128         398 :     fields[iXYUnits].Real = dfXYScale;
     129         398 :     fields[iFalseZ].Real = dfZOrigin;
     130         398 :     fields[iZUnits].Real = dfZScale;
     131         398 :     fields[iFalseM].Real = dfMOrigin;
     132         398 :     fields[iMUnits].Real = dfMScale;
     133         398 :     fields[iXYTolerance].Real = dfXYTolerance;
     134         398 :     fields[iZTolerance].Real = dfZTolerance;
     135         398 :     fields[iMTolerance].Real = dfMTolerance;
     136             : 
     137         398 :     return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
     138             : }
     139             : 
     140             : /***********************************************************************/
     141             : /*                    RegisterLayerInSystemCatalog()                   */
     142             : /***********************************************************************/
     143             : 
     144         267 : bool OGROpenFileGDBDataSource::RegisterLayerInSystemCatalog(
     145             :     const std::string &osLayerName)
     146             : {
     147         534 :     FileGDBTable oTable;
     148         267 :     if (!oTable.Open(m_osGDBSystemCatalogFilename.c_str(), true))
     149           0 :         return false;
     150             : 
     151         267 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
     152         267 :     FETCH_FIELD_IDX(iFileFormat, "FileFormat", FGFT_INT32);
     153             : 
     154         267 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     155         534 :                                  FileGDBField::UNSET_FIELD);
     156         267 :     fields[iName].String = const_cast<char *>(osLayerName.c_str());
     157         267 :     fields[iFileFormat].Integer = 0;
     158         267 :     return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
     159             : }
     160             : 
     161             : /***********************************************************************/
     162             : /*                    RegisterInItemRelationships()                    */
     163             : /***********************************************************************/
     164             : 
     165         294 : bool OGROpenFileGDBDataSource::RegisterInItemRelationships(
     166             :     const std::string &osOriginGUID, const std::string &osDestGUID,
     167             :     const std::string &osTypeGUID)
     168             : {
     169         588 :     FileGDBTable oTable;
     170         294 :     if (!oTable.Open(m_osGDBItemRelationshipsFilename.c_str(), true))
     171           0 :         return false;
     172             : 
     173         294 :     FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
     174         294 :     FETCH_FIELD_IDX(iOriginID, "OriginID", FGFT_GUID);
     175         294 :     FETCH_FIELD_IDX(iDestID, "DestID", FGFT_GUID);
     176         294 :     FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
     177         294 :     FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
     178             : 
     179         294 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     180         882 :                                  FileGDBField::UNSET_FIELD);
     181         294 :     const std::string osGUID = OFGDBGenerateUUID();
     182         294 :     fields[iUUID].String = const_cast<char *>(osGUID.c_str());
     183         294 :     fields[iOriginID].String = const_cast<char *>(osOriginGUID.c_str());
     184         294 :     fields[iDestID].String = const_cast<char *>(osDestGUID.c_str());
     185         294 :     fields[iType].String = const_cast<char *>(osTypeGUID.c_str());
     186         294 :     fields[iProperties].Integer = 1;
     187         294 :     return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
     188             : }
     189             : 
     190             : /***********************************************************************/
     191             : /*              RegisterRelationshipInItemRelationships()              */
     192             : /***********************************************************************/
     193             : 
     194           6 : 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           6 :     if (!RegisterInItemRelationships(osOriginGUID, osRelationshipGUID,
     203             :                                      pszDatasetsRelatedThroughUUID))
     204           0 :         return false;
     205           6 :     if (!RegisterInItemRelationships(osDestGUID, osRelationshipGUID,
     206             :                                      pszDatasetsRelatedThroughUUID))
     207           0 :         return false;
     208           6 :     if (!RegisterInItemRelationships(m_osRootGUID, osRelationshipGUID,
     209             :                                      pszDatasetInFolderUUID))
     210           0 :         return false;
     211             : 
     212           6 :     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          22 : bool OGROpenFileGDBDataSource::FindUUIDFromName(const std::string &osName,
     387             :                                                 std::string &osUUIDOut)
     388             : {
     389          44 :     FileGDBTable oTable;
     390          22 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
     391           0 :         return false;
     392             : 
     393          22 :     FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
     394          22 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
     395             : 
     396         153 :     for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
     397             :          ++iCurFeat)
     398             :     {
     399         152 :         iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
     400         152 :         if (iCurFeat < 0)
     401           0 :             break;
     402         152 :         const auto psName = oTable.GetFieldValue(iName);
     403         152 :         if (psName && psName->String == osName)
     404             :         {
     405          21 :             const auto psUUID = oTable.GetFieldValue(iUUID);
     406          21 :             if (psUUID)
     407             :             {
     408          21 :                 osUUIDOut = psUUID->String;
     409          21 :                 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         195 : 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         390 :     FileGDBTable oTable;
     465         195 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
     466           0 :         return false;
     467             : 
     468         195 :     FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
     469         195 :     FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
     470         195 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
     471         195 :     FETCH_FIELD_IDX(iPhysicalName, "PhysicalName", FGFT_STRING);
     472         195 :     FETCH_FIELD_IDX(iPath, "Path", FGFT_STRING);
     473         195 :     FETCH_FIELD_IDX(iDatasetSubtype1, "DatasetSubtype1", FGFT_INT32);
     474         195 :     FETCH_FIELD_IDX(iDatasetSubtype2, "DatasetSubtype2", FGFT_INT32);
     475         195 :     FETCH_FIELD_IDX(iDatasetInfo1, "DatasetInfo1", FGFT_STRING);
     476         195 :     FETCH_FIELD_IDX(iURL, "URL", FGFT_STRING);
     477         195 :     FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
     478         195 :     FETCH_FIELD_IDX(iDocumentation, "Documentation", FGFT_XML);
     479         195 :     FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
     480             : 
     481         195 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     482         585 :                                  FileGDBField::UNSET_FIELD);
     483         195 :     fields[iUUID].String = const_cast<char *>(osLayerGUID.c_str());
     484         195 :     fields[iType].String = const_cast<char *>(pszFeatureClassTypeUUID);
     485         195 :     fields[iName].String = const_cast<char *>(osLayerName.c_str());
     486         195 :     CPLString osUCName(osLayerName);
     487         195 :     osUCName.toupper();
     488         195 :     fields[iPhysicalName].String = const_cast<char *>(osUCName.c_str());
     489         195 :     fields[iPath].String = const_cast<char *>(osPath.c_str());
     490         195 :     fields[iDatasetSubtype1].Integer = 1;
     491         195 :     fields[iDatasetSubtype2].Integer = poLyrTable->GetGeometryType();
     492         195 :     const auto poGeomFieldDefn = poLyrTable->GetGeomField();
     493         195 :     if (poGeomFieldDefn)  // should always be true
     494         194 :         fields[iDatasetInfo1].String =
     495         194 :             const_cast<char *>(poGeomFieldDefn->GetName().c_str());
     496         195 :     fields[iURL].String = const_cast<char *>("");
     497         195 :     fields[iDefinition].String = const_cast<char *>(pszXMLDefinition);
     498         195 :     if (pszDocumentation && pszDocumentation[0])
     499           1 :         fields[iDocumentation].String = const_cast<char *>(pszDocumentation);
     500         195 :     fields[iProperties].Integer = 1;
     501         195 :     return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
     502             : }
     503             : 
     504             : /***********************************************************************/
     505             : /*                  RegisterASpatialTableInItems()                     */
     506             : /***********************************************************************/
     507             : 
     508          73 : 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         146 :     FileGDBTable oTable;
     514          73 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
     515           0 :         return false;
     516             : 
     517          73 :     FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
     518          73 :     FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
     519          73 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
     520          73 :     FETCH_FIELD_IDX(iPhysicalName, "PhysicalName", FGFT_STRING);
     521          73 :     FETCH_FIELD_IDX(iPath, "Path", FGFT_STRING);
     522          73 :     FETCH_FIELD_IDX(iURL, "URL", FGFT_STRING);
     523          73 :     FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
     524          73 :     FETCH_FIELD_IDX(iDocumentation, "Documentation", FGFT_XML);
     525          73 :     FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
     526             : 
     527          73 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     528         219 :                                  FileGDBField::UNSET_FIELD);
     529          73 :     fields[iUUID].String = const_cast<char *>(osLayerGUID.c_str());
     530          73 :     fields[iType].String = const_cast<char *>(pszTableTypeUUID);
     531          73 :     fields[iName].String = const_cast<char *>(osLayerName.c_str());
     532          73 :     CPLString osUCName(osLayerName);
     533          73 :     osUCName.toupper();
     534          73 :     fields[iPhysicalName].String = const_cast<char *>(osUCName.c_str());
     535          73 :     fields[iPath].String = const_cast<char *>(osPath.c_str());
     536          73 :     fields[iURL].String = const_cast<char *>("");
     537          73 :     fields[iDefinition].String = const_cast<char *>(pszXMLDefinition);
     538          73 :     if (pszDocumentation && pszDocumentation[0])
     539           0 :         fields[iDocumentation].String = const_cast<char *>(pszDocumentation);
     540          73 :     fields[iProperties].Integer = 1;
     541          73 :     return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
     542             : }
     543             : 
     544             : /***********************************************************************/
     545             : /*                       CreateGDBSystemCatalog()                      */
     546             : /***********************************************************************/
     547             : 
     548         228 : bool OGROpenFileGDBDataSource::CreateGDBSystemCatalog()
     549             : {
     550             :     // Write GDB_SystemCatalog file
     551             :     m_osGDBSystemCatalogFilename =
     552         228 :         CPLFormFilename(m_osDirName.c_str(), "a00000001.gdbtable", nullptr);
     553         456 :     FileGDBTable oTable;
     554         228 :     if (!oTable.Create(m_osGDBSystemCatalogFilename.c_str(), 4, FGTGT_NONE,
     555         456 :                        false, false) ||
     556         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     557         228 :             "ID", std::string(), FGFT_OBJECTID,
     558           0 :             /* bNullable = */ false,
     559           0 :             /* bRequired = */ true,
     560         456 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
     561         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     562         228 :             "Name", std::string(), FGFT_STRING,
     563           0 :             /* bNullable = */ false,
     564           0 :             /* bRequired = */ false,
     565        1140 :             /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) ||
     566         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     567         228 :             "FileFormat", std::string(), FGFT_INT32,
     568           0 :             /* bNullable = */ false,
     569           0 :             /* bRequired = */ false,
     570         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)))
     571             :     {
     572           0 :         return false;
     573             :     }
     574             : 
     575         228 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     576         684 :                                  FileGDBField::UNSET_FIELD);
     577             : 
     578        1824 :     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        3876 :              {"GDB_ReplicaLog", 2}})
     587             :     {
     588        1824 :         fields[1].String = const_cast<char *>(pair.first);
     589        1824 :         fields[2].Integer = pair.second;
     590        1824 :         if (!oTable.CreateFeature(fields, nullptr))
     591           0 :             return false;
     592             :     }
     593             : 
     594         228 :     m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
     595         228 :         this, m_osGDBSystemCatalogFilename.c_str(), "GDB_SystemCatalog", "", "",
     596         456 :         true));
     597             : 
     598         228 :     return oTable.Sync();
     599             : }
     600             : 
     601             : /***********************************************************************/
     602             : /*                       CreateGDBDBTune()                             */
     603             : /***********************************************************************/
     604             : 
     605         228 : bool OGROpenFileGDBDataSource::CreateGDBDBTune()
     606             : {
     607             :     // Write GDB_DBTune file
     608             :     const std::string osFilename(
     609         456 :         CPLFormFilename(m_osDirName.c_str(), "a00000002.gdbtable", nullptr));
     610         456 :     FileGDBTable oTable;
     611         228 :     if (!oTable.Create(osFilename.c_str(), 4, FGTGT_NONE, false, false) ||
     612         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     613         228 :             "Keyword", std::string(), FGFT_STRING,
     614           0 :             /* bNullable = */ false,
     615           0 :             /* bRequired = */ false,
     616         456 :             /* bEditable = */ true, 32, FileGDBField::UNSET_FIELD)) ||
     617         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     618         228 :             "ParameterName", std::string(), FGFT_STRING,
     619           0 :             /* bNullable = */ false,
     620           0 :             /* bRequired = */ false,
     621        1140 :             /* bEditable = */ true, 32, FileGDBField::UNSET_FIELD)) ||
     622         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     623         228 :             "ConfigString", std::string(), FGFT_STRING,
     624           0 :             /* bNullable = */ true,
     625           0 :             /* bRequired = */ false,
     626         456 :             /* bEditable = */ true, 2048, FileGDBField::UNSET_FIELD)))
     627             :     {
     628           0 :         return false;
     629             :     }
     630             : 
     631         228 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     632         684 :                                  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        8208 :     for (const auto &record : apsData)
     685             :     {
     686        7980 :         fields[0].String = const_cast<char *>(record.pszKeyword);
     687        7980 :         fields[1].String = const_cast<char *>(record.pszParameterName);
     688        7980 :         fields[2].String = const_cast<char *>(record.pszConfigString);
     689        7980 :         if (!oTable.CreateFeature(fields, nullptr))
     690           0 :             return false;
     691             :     }
     692             : 
     693         228 :     m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
     694         228 :         this, osFilename.c_str(), "GDB_DBTune", "", "", true));
     695             : 
     696         228 :     return oTable.Sync();
     697             : }
     698             : 
     699             : /***********************************************************************/
     700             : /*                       CreateGDBSpatialRefs()                        */
     701             : /***********************************************************************/
     702             : 
     703         228 : bool OGROpenFileGDBDataSource::CreateGDBSpatialRefs()
     704             : {
     705             :     // Write GDB_SpatialRefs file
     706             :     m_osGDBSpatialRefsFilename =
     707         228 :         CPLFormFilename(m_osDirName.c_str(), "a00000003.gdbtable", nullptr);
     708         456 :     FileGDBTable oTable;
     709         228 :     if (!oTable.Create(m_osGDBSpatialRefsFilename.c_str(), 4, FGTGT_NONE, false,
     710         456 :                        false) ||
     711         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     712         228 :             "ID", std::string(), FGFT_OBJECTID,
     713           0 :             /* bNullable = */ false,
     714           0 :             /* bRequired = */ true,
     715         456 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
     716         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     717         228 :             "SRTEXT", std::string(), FGFT_STRING,
     718           0 :             /* bNullable = */ false,
     719           0 :             /* bRequired = */ false,
     720         456 :             /* bEditable = */ true, 2048, FileGDBField::UNSET_FIELD)) ||
     721         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     722         228 :             "FalseX", std::string(), FGFT_FLOAT64,
     723           0 :             /* bNullable = */ true,
     724           0 :             /* bRequired = */ false,
     725         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     726         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     727         228 :             "FalseY", std::string(), FGFT_FLOAT64,
     728           0 :             /* bNullable = */ true,
     729           0 :             /* bRequired = */ false,
     730         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     731         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     732         228 :             "XYUnits", std::string(), FGFT_FLOAT64,
     733           0 :             /* bNullable = */ true,
     734           0 :             /* bRequired = */ false,
     735         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     736         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     737         228 :             "FalseZ", std::string(), FGFT_FLOAT64,
     738           0 :             /* bNullable = */ true,
     739           0 :             /* bRequired = */ false,
     740         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     741         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     742         228 :             "ZUnits", std::string(), FGFT_FLOAT64,
     743           0 :             /* bNullable = */ true,
     744           0 :             /* bRequired = */ false,
     745         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     746         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     747         228 :             "FalseM", std::string(), FGFT_FLOAT64,
     748           0 :             /* bNullable = */ true,
     749           0 :             /* bRequired = */ false,
     750         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     751         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     752         228 :             "MUnits", std::string(), FGFT_FLOAT64,
     753           0 :             /* bNullable = */ true,
     754           0 :             /* bRequired = */ false,
     755         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     756         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     757         228 :             "XYTolerance", std::string(), FGFT_FLOAT64,
     758           0 :             /* bNullable = */ true,
     759           0 :             /* bRequired = */ false,
     760         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     761         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     762         228 :             "ZTolerance", std::string(), FGFT_FLOAT64,
     763           0 :             /* bNullable = */ true,
     764           0 :             /* bRequired = */ false,
     765        1140 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     766         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     767         228 :             "MTolerance", std::string(), FGFT_FLOAT64,
     768           0 :             /* bNullable = */ true,
     769           0 :             /* bRequired = */ false,
     770         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)))
     771             :     {
     772           0 :         return false;
     773             :     }
     774             : 
     775         228 :     m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
     776         228 :         this, m_osGDBSpatialRefsFilename.c_str(), "GDB_SpatialRefs", "", "",
     777         456 :         true));
     778             : 
     779         228 :     return oTable.Sync();
     780             : }
     781             : 
     782             : /***********************************************************************/
     783             : /*                       CreateGDBItems()                              */
     784             : /***********************************************************************/
     785             : 
     786         228 : bool OGROpenFileGDBDataSource::CreateGDBItems()
     787             : {
     788             :     // Write GDB_Items file
     789         228 :     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         684 :                              1000000, 0.000002, {0.012, 0.4, 12.0}));
     796         228 :     poGeomField->SetZOriginScaleTolerance(-100000, 10000, 0.001);
     797         228 :     poGeomField->SetMOriginScaleTolerance(-100000, 10000, 0.001);
     798             : 
     799         228 :     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         228 :         CPLFormFilename(m_osDirName.c_str(), "a00000004.gdbtable", nullptr);
     812         456 :     FileGDBTable oTable;
     813         228 :     if (!oTable.Create(m_osGDBItemsFilename.c_str(), 4, FGTGT_POLYGON, false,
     814         456 :                        false) ||
     815         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     816         228 :             "ObjectID", std::string(), FGFT_OBJECTID,
     817           0 :             /* bNullable = */ false,
     818           0 :             /* bRequired = */ true,
     819         456 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
     820         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     821         228 :             "UUID", std::string(), FGFT_GLOBALID,
     822           0 :             /* bNullable = */ false,
     823           0 :             /* bRequired = */ true,
     824         456 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
     825         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     826         228 :             "Type", std::string(), FGFT_GUID,
     827           0 :             /* bNullable = */ false,
     828           0 :             /* bRequired = */ false,
     829         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     830         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     831         228 :             "Name", std::string(), FGFT_STRING,
     832           0 :             /* bNullable = */ true,
     833           0 :             /* bRequired = */ false,
     834         456 :             /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) ||
     835         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     836         228 :             "PhysicalName", std::string(), FGFT_STRING,
     837           0 :             /* bNullable = */ true,
     838           0 :             /* bRequired = */ false,
     839         456 :             /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) ||
     840         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     841         228 :             "Path", std::string(), FGFT_STRING,
     842           0 :             /* bNullable = */ true,
     843           0 :             /* bRequired = */ false,
     844         456 :             /* bEditable = */ true, 260, FileGDBField::UNSET_FIELD)) ||
     845         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     846         228 :             "DatasetSubtype1", std::string(), FGFT_INT32,
     847           0 :             /* bNullable = */ true,
     848           0 :             /* bRequired = */ false,
     849         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     850         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     851         228 :             "DatasetSubtype2", std::string(), FGFT_INT32,
     852           0 :             /* bNullable = */ true,
     853           0 :             /* bRequired = */ false,
     854         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     855         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     856         228 :             "DatasetInfo1", std::string(), FGFT_STRING,
     857           0 :             /* bNullable = */ true,
     858           0 :             /* bRequired = */ false,
     859         456 :             /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) ||
     860         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     861         228 :             "DatasetInfo2", std::string(), FGFT_STRING,
     862           0 :             /* bNullable = */ true,
     863           0 :             /* bRequired = */ false,
     864         456 :             /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) ||
     865         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     866         228 :             "URL", std::string(), FGFT_STRING,
     867           0 :             /* bNullable = */ true,
     868           0 :             /* bRequired = */ false,
     869         456 :             /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) ||
     870         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     871         228 :             "Definition", std::string(), FGFT_XML,
     872           0 :             /* bNullable = */ true,
     873           0 :             /* bRequired = */ false,
     874         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     875         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     876         228 :             "Documentation", std::string(), FGFT_XML,
     877           0 :             /* bNullable = */ true,
     878           0 :             /* bRequired = */ false,
     879         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     880         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     881         228 :             "ItemInfo", std::string(), FGFT_XML,
     882           0 :             /* bNullable = */ true,
     883           0 :             /* bRequired = */ false,
     884         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     885         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     886         228 :             "Properties", std::string(), FGFT_INT32,
     887           0 :             /* bNullable = */ true,
     888           0 :             /* bRequired = */ false,
     889         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     890         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     891         228 :             "Defaults", std::string(), FGFT_BINARY,
     892           0 :             /* bNullable = */ true,
     893           0 :             /* bRequired = */ false,
     894        1140 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     895         456 :         !oTable.CreateField(std::move(poGeomField)))
     896             :     {
     897           0 :         return false;
     898             :     }
     899             : 
     900         228 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     901         684 :                                  FileGDBField::UNSET_FIELD);
     902         228 :     m_osRootGUID = OFGDBGenerateUUID();
     903         228 :     fields[1].String = const_cast<char *>(m_osRootGUID.c_str());
     904         228 :     fields[2].String = const_cast<char *>(pszFolderTypeUUID);
     905         228 :     fields[3].String = const_cast<char *>("");
     906         228 :     fields[4].String = const_cast<char *>("");
     907         228 :     fields[5].String = const_cast<char *>("\\");
     908         228 :     fields[10].String = const_cast<char *>("");
     909         228 :     fields[14].Integer = 1;
     910         228 :     if (!oTable.CreateFeature(fields, nullptr))
     911           0 :         return false;
     912             : 
     913         228 :     const std::string osWorkspaceUUID(OFGDBGenerateUUID());
     914         228 :     fields[1].String = const_cast<char *>(osWorkspaceUUID.c_str());
     915         228 :     fields[2].String = const_cast<char *>(pszWorkspaceTypeUUID);
     916         228 :     fields[3].String = const_cast<char *>("Workspace");
     917         228 :     fields[4].String = const_cast<char *>("WORKSPACE");
     918         228 :     fields[5].String = const_cast<char *>("");   // Path
     919         228 :     fields[10].String = const_cast<char *>("");  // URL
     920         228 :     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         228 :     fields[14].Integer = 0;
     939             : 
     940         228 :     m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
     941         228 :         this, m_osGDBItemsFilename.c_str(), "GDB_Items", "", "", true));
     942             : 
     943         228 :     return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
     944             : }
     945             : 
     946             : /***********************************************************************/
     947             : /*                       CreateGDBItemTypes()                          */
     948             : /***********************************************************************/
     949             : 
     950         228 : bool OGROpenFileGDBDataSource::CreateGDBItemTypes()
     951             : {
     952             :     // Write GDB_ItemTypes file
     953             :     const std::string osFilename(
     954         456 :         CPLFormFilename(m_osDirName.c_str(), "a00000005.gdbtable", nullptr));
     955         456 :     FileGDBTable oTable;
     956         228 :     if (!oTable.Create(osFilename.c_str(), 4, FGTGT_NONE, false, false) ||
     957         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     958         228 :             "ObjectID", std::string(), FGFT_OBJECTID,
     959           0 :             /* bNullable = */ false,
     960           0 :             /* bRequired = */ true,
     961         456 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
     962         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     963         228 :             "UUID", std::string(), FGFT_GUID,
     964           0 :             /* bNullable = */ false,
     965           0 :             /* bRequired = */ false,
     966         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     967         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     968         228 :             "ParentTypeID", std::string(), FGFT_GUID,
     969           0 :             /* bNullable = */ false,
     970           0 :             /* bRequired = */ false,
     971        1140 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
     972         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
     973         228 :             "Name", std::string(), FGFT_STRING,
     974           0 :             /* bNullable = */ false,
     975           0 :             /* bRequired = */ false,
     976         456 :             /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)))
     977             :     {
     978           0 :         return false;
     979             :     }
     980             : 
     981         228 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
     982         684 :                                  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        7524 :     for (const auto &record : apsData)
    1054             :     {
    1055        7296 :         fields[1].String = const_cast<char *>(record.pszUUID);
    1056        7296 :         fields[2].String = const_cast<char *>(record.pszParentTypeID);
    1057        7296 :         fields[3].String = const_cast<char *>(record.pszName);
    1058        7296 :         if (!oTable.CreateFeature(fields, nullptr))
    1059           0 :             return false;
    1060             :     }
    1061             : 
    1062         228 :     m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
    1063         228 :         this, osFilename.c_str(), "GDB_ItemTypes", "", "", true));
    1064             : 
    1065         228 :     return oTable.Sync();
    1066             : }
    1067             : 
    1068             : /***********************************************************************/
    1069             : /*                  CreateGDBItemRelationships()                       */
    1070             : /***********************************************************************/
    1071             : 
    1072         228 : bool OGROpenFileGDBDataSource::CreateGDBItemRelationships()
    1073             : {
    1074             :     // Write GDB_ItemRelationships file
    1075             :     m_osGDBItemRelationshipsFilename =
    1076         228 :         CPLFormFilename(m_osDirName.c_str(), "a00000006.gdbtable", nullptr);
    1077         456 :     FileGDBTable oTable;
    1078         228 :     if (!oTable.Create(m_osGDBItemRelationshipsFilename.c_str(), 4, FGTGT_NONE,
    1079         456 :                        false, false) ||
    1080         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1081         228 :             "ObjectID", std::string(), FGFT_OBJECTID,
    1082           0 :             /* bNullable = */ false,
    1083           0 :             /* bRequired = */ true,
    1084         456 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
    1085         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1086         228 :             "UUID", std::string(), FGFT_GLOBALID,
    1087           0 :             /* bNullable = */ false,
    1088           0 :             /* bRequired = */ true,
    1089         456 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
    1090         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1091         228 :             "OriginID", std::string(), FGFT_GUID,
    1092           0 :             /* bNullable = */ false,
    1093           0 :             /* bRequired = */ false,
    1094         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
    1095         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1096         228 :             "DestID", std::string(), FGFT_GUID,
    1097           0 :             /* bNullable = */ false,
    1098           0 :             /* bRequired = */ false,
    1099         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
    1100         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1101         228 :             "Type", std::string(), FGFT_GUID,
    1102           0 :             /* bNullable = */ false,
    1103           0 :             /* bRequired = */ false,
    1104         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
    1105         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1106         228 :             "Attributes", std::string(), FGFT_XML,
    1107           0 :             /* bNullable = */ true,
    1108           0 :             /* bRequired = */ false,
    1109        1140 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
    1110         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1111         228 :             "Properties", std::string(), FGFT_INT32,
    1112           0 :             /* bNullable = */ true,
    1113           0 :             /* bRequired = */ false,
    1114         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)))
    1115             :     {
    1116           0 :         return false;
    1117             :     }
    1118             : 
    1119         228 :     m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
    1120         228 :         this, m_osGDBItemRelationshipsFilename.c_str(), "GDB_ItemRelationships",
    1121         456 :         "", "", true));
    1122             : 
    1123         228 :     return oTable.Sync();
    1124             : }
    1125             : 
    1126             : /***********************************************************************/
    1127             : /*                 CreateGDBItemRelationshipTypes()                    */
    1128             : /***********************************************************************/
    1129             : 
    1130         228 : bool OGROpenFileGDBDataSource::CreateGDBItemRelationshipTypes()
    1131             : {
    1132             :     // Write GDB_ItemRelationshipTypes file
    1133             :     const std::string osFilename(
    1134         456 :         CPLFormFilename(m_osDirName.c_str(), "a00000007.gdbtable", nullptr));
    1135         456 :     FileGDBTable oTable;
    1136         228 :     if (!oTable.Create(osFilename.c_str(), 4, FGTGT_NONE, false, false) ||
    1137         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1138         228 :             "ObjectID", std::string(), FGFT_OBJECTID,
    1139           0 :             /* bNullable = */ false,
    1140           0 :             /* bRequired = */ true,
    1141         456 :             /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
    1142         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1143         228 :             "UUID", std::string(), FGFT_GUID,
    1144           0 :             /* bNullable = */ false,
    1145           0 :             /* bRequired = */ false,
    1146         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
    1147         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1148         228 :             "OrigItemTypeID", std::string(), FGFT_GUID,
    1149           0 :             /* bNullable = */ false,
    1150           0 :             /* bRequired = */ false,
    1151         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
    1152         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1153         228 :             "DestItemTypeID", std::string(), FGFT_GUID,
    1154           0 :             /* bNullable = */ false,
    1155           0 :             /* bRequired = */ false,
    1156         456 :             /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
    1157         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1158         228 :             "Name", std::string(), FGFT_STRING,
    1159           0 :             /* bNullable = */ true,
    1160           0 :             /* bRequired = */ false,
    1161         456 :             /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) ||
    1162         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1163         228 :             "ForwardLabel", std::string(), FGFT_STRING,
    1164           0 :             /* bNullable = */ true,
    1165           0 :             /* bRequired = */ false,
    1166         456 :             /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) ||
    1167         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1168         228 :             "BackwardLabel", std::string(), FGFT_STRING,
    1169           0 :             /* bNullable = */ true,
    1170           0 :             /* bRequired = */ false,
    1171        1140 :             /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) ||
    1172         456 :         !oTable.CreateField(std::make_unique<FileGDBField>(
    1173         228 :             "IsContainment", std::string(), FGFT_INT16,
    1174           0 :             /* bNullable = */ true,
    1175           0 :             /* bRequired = */ false,
    1176         456 :             /* 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         228 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
    1253         684 :                                  FileGDBField::UNSET_FIELD);
    1254        3876 :     for (const auto &record : apsData)
    1255             :     {
    1256        3648 :         fields[1].String = const_cast<char *>(record.pszUUID);
    1257        3648 :         fields[2].String = const_cast<char *>(record.pszOrigItemTypeID);
    1258        3648 :         fields[3].String = const_cast<char *>(record.pszDestItemTypeID);
    1259        3648 :         fields[4].String = const_cast<char *>(record.pszName);
    1260        3648 :         fields[5].String = const_cast<char *>(record.pszForwardLabel);
    1261        3648 :         fields[6].String = const_cast<char *>(record.pszBackwardLabel);
    1262        3648 :         fields[7].Integer = record.IsContainment;
    1263        3648 :         if (!oTable.CreateFeature(fields, nullptr))
    1264           0 :             return false;
    1265             :     }
    1266             : 
    1267         228 :     m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
    1268         228 :         this, osFilename.c_str(), "GDB_ItemRelationshipTypes", "", "", true));
    1269             : 
    1270         228 :     return oTable.Sync();
    1271             : }
    1272             : 
    1273             : /***********************************************************************/
    1274             : /*                             Create()                                */
    1275             : /***********************************************************************/
    1276             : 
    1277         231 : bool OGROpenFileGDBDataSource::Create(const char *pszName)
    1278             : {
    1279             : 
    1280         231 :     if (!EQUAL(CPLGetExtension(pszName), "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         230 :     if (VSIStatL(pszName, &sStat) == 0)
    1290             :     {
    1291           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s already exists.", pszName);
    1292           0 :         return false;
    1293             :     }
    1294             : 
    1295         230 :     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         228 :     CPL_IGNORE_RET_VAL(OFGDBGenerateUUID(/* bInit = */ true));
    1303             : 
    1304         228 :     m_osDirName = pszName;
    1305         228 :     eAccess = GA_Update;
    1306             : 
    1307             :     {
    1308             :         // Write "gdb" file
    1309         228 :         const std::string osFilename(CPLFormFilename(pszName, "gdb", nullptr));
    1310         228 :         VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
    1311         228 :         if (!fp)
    1312           0 :             return false;
    1313             :         // Write what the FileGDB SDK writes...
    1314         228 :         VSIFWriteL("\x05\x00\x00\x00\xDE\xAD\xBE\xEF", 1, 8, fp);
    1315         228 :         VSIFCloseL(fp);
    1316             :     }
    1317             : 
    1318             :     {
    1319             :         // Write "timestamps" file
    1320             :         const std::string osFilename(
    1321         228 :             CPLFormFilename(pszName, "timestamps", nullptr));
    1322         228 :         VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
    1323         228 :         if (!fp)
    1324           0 :             return false;
    1325             :         // Write what the FileGDB SDK writes...
    1326         456 :         std::vector<GByte> values(400, 0xFF);
    1327         228 :         VSIFWriteL(values.data(), 1, values.size(), fp);
    1328         228 :         VSIFCloseL(fp);
    1329             :     }
    1330             : 
    1331         456 :     return CreateGDBSystemCatalog() && CreateGDBDBTune() &&
    1332         228 :            CreateGDBSpatialRefs() && CreateGDBItems() && CreateGDBItemTypes() &&
    1333         456 :            CreateGDBItemRelationships() && CreateGDBItemRelationshipTypes();
    1334             :     // GDB_ReplicaLog can be omitted.
    1335             : }
    1336             : 
    1337             : /************************************************************************/
    1338             : /*                             ICreateLayer()                           */
    1339             : /************************************************************************/
    1340             : 
    1341             : OGRLayer *
    1342         270 : OGROpenFileGDBDataSource::ICreateLayer(const char *pszLayerName,
    1343             :                                        const OGRGeomFieldDefn *poGeomFieldDefn,
    1344             :                                        CSLConstList papszOptions)
    1345             : {
    1346         270 :     if (eAccess != GA_Update)
    1347           0 :         return nullptr;
    1348             : 
    1349         270 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    1350           0 :         return nullptr;
    1351             : 
    1352         270 :     if (m_osRootGUID.empty())
    1353             :     {
    1354           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Root UUID missing");
    1355           0 :         return nullptr;
    1356             :     }
    1357             : 
    1358         270 :     auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
    1359             : 
    1360         540 :     FileGDBTable oTable;
    1361         540 :     if (!oTable.Open(m_osGDBSystemCatalogFilename.c_str(), false) ||
    1362         270 :         oTable.GetTotalRecordCount() >= INT32_MAX)
    1363           0 :         return nullptr;
    1364         270 :     const int nTableNum = static_cast<int>(1 + oTable.GetTotalRecordCount());
    1365         270 :     oTable.Close();
    1366             : 
    1367             :     const std::string osFilename(CPLFormFilename(
    1368         540 :         m_osDirName.c_str(), CPLSPrintf("a%08x.gdbtable", nTableNum), nullptr));
    1369             : 
    1370         270 :     if (wkbFlatten(eType) == wkbLineString)
    1371          16 :         eType = OGR_GT_SetModifier(wkbMultiLineString, OGR_GT_HasZ(eType),
    1372             :                                    OGR_GT_HasM(eType));
    1373         254 :     else if (wkbFlatten(eType) == wkbPolygon)
    1374          17 :         eType = OGR_GT_SetModifier(wkbMultiPolygon, OGR_GT_HasZ(eType),
    1375             :                                    OGR_GT_HasM(eType));
    1376             : 
    1377             :     auto poLayer = std::make_unique<OGROpenFileGDBLayer>(
    1378         540 :         this, osFilename.c_str(), pszLayerName, eType, papszOptions);
    1379         270 :     if (!poLayer->Create(poGeomFieldDefn))
    1380           3 :         return nullptr;
    1381         267 :     if (m_bInTransaction)
    1382             :     {
    1383           4 :         if (!poLayer->BeginEmulatedTransaction())
    1384           0 :             return nullptr;
    1385           4 :         m_oSetLayersCreatedInTransaction.insert(poLayer.get());
    1386             :     }
    1387         267 :     m_apoLayers.emplace_back(std::move(poLayer));
    1388             : 
    1389         267 :     return m_apoLayers.back().get();
    1390             : }
    1391             : 
    1392             : /************************************************************************/
    1393             : /*                            DeleteLayer()                             */
    1394             : /************************************************************************/
    1395             : 
    1396          19 : OGRErr OGROpenFileGDBDataSource::DeleteLayer(int iLayer)
    1397             : {
    1398          19 :     if (eAccess != GA_Update)
    1399           0 :         return OGRERR_FAILURE;
    1400             : 
    1401          19 :     if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
    1402           2 :         return OGRERR_FAILURE;
    1403             : 
    1404          17 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    1405           0 :         return false;
    1406             : 
    1407          17 :     auto poLayer = m_apoLayers[iLayer].get();
    1408             : 
    1409             :     // Remove from GDB_SystemCatalog
    1410             :     {
    1411          17 :         FileGDBTable oTable;
    1412          17 :         if (!oTable.Open(m_osGDBSystemCatalogFilename.c_str(), true))
    1413           0 :             return OGRERR_FAILURE;
    1414             : 
    1415          17 :         FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, OGRERR_FAILURE);
    1416             : 
    1417         153 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    1418             :              ++iCurFeat)
    1419             :         {
    1420         153 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    1421         153 :             if (iCurFeat < 0)
    1422           0 :                 break;
    1423         153 :             const auto psName = oTable.GetFieldValue(iName);
    1424         153 :             if (psName && strcmp(psName->String, poLayer->GetName()) == 0)
    1425             :             {
    1426          17 :                 oTable.DeleteFeature(iCurFeat + 1);
    1427          17 :                 break;
    1428             :             }
    1429             :         }
    1430             :     }
    1431             : 
    1432             :     // Remove from GDB_Items
    1433          34 :     std::string osUUID;
    1434             :     {
    1435          17 :         FileGDBTable oTable;
    1436          17 :         if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
    1437           0 :             return OGRERR_FAILURE;
    1438             : 
    1439          17 :         FETCH_FIELD_IDX_WITH_RET(iUUID, "UUID", FGFT_GLOBALID, OGRERR_FAILURE);
    1440          17 :         FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, OGRERR_FAILURE);
    1441             : 
    1442          51 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    1443             :              ++iCurFeat)
    1444             :         {
    1445          37 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    1446          37 :             if (iCurFeat < 0)
    1447           0 :                 break;
    1448          37 :             const auto psName = oTable.GetFieldValue(iName);
    1449          37 :             if (psName && strcmp(psName->String, poLayer->GetName()) == 0)
    1450             :             {
    1451           3 :                 const auto psUUID = oTable.GetFieldValue(iUUID);
    1452           3 :                 if (psUUID)
    1453             :                 {
    1454           3 :                     osUUID = psUUID->String;
    1455             :                 }
    1456             : 
    1457           3 :                 oTable.DeleteFeature(iCurFeat + 1);
    1458           3 :                 break;
    1459             :             }
    1460             :         }
    1461             :     }
    1462             : 
    1463             :     // Remove from GDB_ItemRelationships
    1464          17 :     if (!osUUID.empty())
    1465             :     {
    1466           3 :         FileGDBTable oTable;
    1467           3 :         if (!oTable.Open(m_osGDBItemRelationshipsFilename.c_str(), true))
    1468           0 :             return OGRERR_FAILURE;
    1469             : 
    1470           3 :         FETCH_FIELD_IDX_WITH_RET(iOriginID, "OriginID", FGFT_GUID,
    1471             :                                  OGRERR_FAILURE);
    1472           3 :         FETCH_FIELD_IDX_WITH_RET(iDestID, "DestID", FGFT_GUID, OGRERR_FAILURE);
    1473             : 
    1474           7 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    1475             :              ++iCurFeat)
    1476             :         {
    1477           4 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    1478           4 :             if (iCurFeat < 0)
    1479           0 :                 break;
    1480             : 
    1481           4 :             const auto psOriginID = oTable.GetFieldValue(iOriginID);
    1482           4 :             if (psOriginID && psOriginID->String == osUUID)
    1483             :             {
    1484           0 :                 oTable.DeleteFeature(iCurFeat + 1);
    1485             :             }
    1486             :             else
    1487             :             {
    1488           4 :                 const auto psDestID = oTable.GetFieldValue(iDestID);
    1489           4 :                 if (psDestID && psDestID->String == osUUID)
    1490             :                 {
    1491           3 :                     oTable.DeleteFeature(iCurFeat + 1);
    1492             :                 }
    1493             :             }
    1494             :         }
    1495             :     }
    1496             : 
    1497          34 :     const std::string osDirname = CPLGetPath(poLayer->GetFilename().c_str());
    1498             :     const std::string osFilenameBase =
    1499          17 :         CPLGetBasename(poLayer->GetFilename().c_str());
    1500             : 
    1501          17 :     if (m_bInTransaction)
    1502             :     {
    1503             :         auto oIter =
    1504           2 :             m_oSetLayersCreatedInTransaction.find(m_apoLayers[iLayer].get());
    1505           2 :         if (oIter != m_oSetLayersCreatedInTransaction.end())
    1506             :         {
    1507           1 :             m_oSetLayersCreatedInTransaction.erase(oIter);
    1508             :         }
    1509             :         else
    1510             :         {
    1511           1 :             poLayer->BeginEmulatedTransaction();
    1512           1 :             poLayer->Close();
    1513             :             m_oSetLayersDeletedInTransaction.insert(
    1514           1 :                 std::move(m_apoLayers[iLayer]));
    1515             :         }
    1516             :     }
    1517             : 
    1518             :     // Delete OGR layer
    1519          17 :     m_apoLayers.erase(m_apoLayers.begin() + iLayer);
    1520             : 
    1521             :     // Remove files associated with the layer
    1522          17 :     char **papszFiles = VSIReadDir(osDirname.c_str());
    1523         408 :     for (char **papszIter = papszFiles; papszIter && *papszIter; ++papszIter)
    1524             :     {
    1525         391 :         if (STARTS_WITH(*papszIter, osFilenameBase.c_str()))
    1526             :         {
    1527          59 :             VSIUnlink(CPLFormFilename(osDirname.c_str(), *papszIter, nullptr));
    1528             :         }
    1529             :     }
    1530          17 :     CSLDestroy(papszFiles);
    1531             : 
    1532          17 :     return OGRERR_NONE;
    1533             : }
    1534             : 
    1535             : /************************************************************************/
    1536             : /*                             FlushCache()                             */
    1537             : /************************************************************************/
    1538             : 
    1539         851 : CPLErr OGROpenFileGDBDataSource::FlushCache(bool /*bAtClosing*/)
    1540             : {
    1541         851 :     if (eAccess != GA_Update)
    1542         559 :         return CE_None;
    1543             : 
    1544         292 :     CPLErr eErr = CE_None;
    1545         688 :     for (auto &poLayer : m_apoLayers)
    1546             :     {
    1547         396 :         if (poLayer->SyncToDisk() != OGRERR_NONE)
    1548           0 :             eErr = CE_Failure;
    1549             :     }
    1550         292 :     return eErr;
    1551             : }
    1552             : 
    1553             : /************************************************************************/
    1554             : /*                          AddFieldDomain()                            */
    1555             : /************************************************************************/
    1556             : 
    1557           6 : bool OGROpenFileGDBDataSource::AddFieldDomain(
    1558             :     std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
    1559             : {
    1560          12 :     const std::string domainName(domain->GetName());
    1561           6 :     if (eAccess != GA_Update)
    1562             :     {
    1563           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1564             :                  "AddFieldDomain() not supported on read-only dataset");
    1565           0 :         return false;
    1566             :     }
    1567             : 
    1568           6 :     if (GetFieldDomain(domainName) != nullptr)
    1569             :     {
    1570           0 :         failureReason = "A domain of identical name already exists";
    1571           0 :         return false;
    1572             :     }
    1573             : 
    1574           6 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    1575           0 :         return false;
    1576             : 
    1577             :     std::string osXML =
    1578          12 :         BuildXMLFieldDomainDef(domain.get(), false, failureReason);
    1579           6 :     if (osXML.empty())
    1580             :     {
    1581           0 :         return false;
    1582             :     }
    1583             : 
    1584          12 :     const std::string osThisGUID = OFGDBGenerateUUID();
    1585             : 
    1586          12 :     FileGDBTable oTable;
    1587           6 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
    1588           0 :         return false;
    1589             : 
    1590           6 :     FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
    1591           6 :     FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
    1592           6 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
    1593           6 :     FETCH_FIELD_IDX(iPhysicalName, "PhysicalName", FGFT_STRING);
    1594           6 :     FETCH_FIELD_IDX(iPath, "Path", FGFT_STRING);
    1595           6 :     FETCH_FIELD_IDX(iURL, "URL", FGFT_STRING);
    1596           6 :     FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
    1597           6 :     FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
    1598             : 
    1599           6 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
    1600          18 :                                  FileGDBField::UNSET_FIELD);
    1601           6 :     fields[iUUID].String = const_cast<char *>(osThisGUID.c_str());
    1602           6 :     switch (domain->GetDomainType())
    1603             :     {
    1604           4 :         case OFDT_CODED:
    1605           4 :             fields[iType].String = const_cast<char *>(pszCodedDomainTypeUUID);
    1606           4 :             break;
    1607             : 
    1608           2 :         case OFDT_RANGE:
    1609           2 :             fields[iType].String = const_cast<char *>(pszRangeDomainTypeUUID);
    1610           2 :             break;
    1611             : 
    1612           0 :         case OFDT_GLOB:
    1613           0 :             CPLAssert(false);
    1614             :             break;
    1615             :     }
    1616           6 :     fields[iName].String = const_cast<char *>(domainName.c_str());
    1617          12 :     CPLString osUCName(domainName);
    1618           6 :     osUCName.toupper();
    1619           6 :     fields[iPhysicalName].String = const_cast<char *>(osUCName.c_str());
    1620           6 :     fields[iPath].String = const_cast<char *>("");
    1621           6 :     fields[iURL].String = const_cast<char *>("");
    1622           6 :     fields[iDefinition].String = const_cast<char *>(osXML.c_str());
    1623           6 :     fields[iProperties].Integer = 1;
    1624             : 
    1625           6 :     if (!(oTable.CreateFeature(fields, nullptr) && oTable.Sync()))
    1626           0 :         return false;
    1627             : 
    1628           6 :     m_oMapFieldDomains[domainName] = std::move(domain);
    1629             : 
    1630           6 :     return true;
    1631             : }
    1632             : 
    1633             : /************************************************************************/
    1634             : /*                         DeleteFieldDomain()                          */
    1635             : /************************************************************************/
    1636             : 
    1637           2 : bool OGROpenFileGDBDataSource::DeleteFieldDomain(
    1638             :     const std::string &name, std::string & /*failureReason*/)
    1639             : {
    1640           2 :     if (eAccess != GA_Update)
    1641             :     {
    1642           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1643             :                  "DeleteFieldDomain() not supported on read-only dataset");
    1644           0 :         return false;
    1645             :     }
    1646             : 
    1647           2 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    1648           0 :         return false;
    1649             : 
    1650             :     // Remove object from GDB_Items
    1651           4 :     std::string osUUID;
    1652             :     {
    1653           2 :         FileGDBTable oTable;
    1654           2 :         if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
    1655           0 :             return false;
    1656             : 
    1657           2 :         FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
    1658           2 :         FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
    1659           2 :         FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
    1660             : 
    1661          14 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    1662             :              ++iCurFeat)
    1663             :         {
    1664          13 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    1665          13 :             if (iCurFeat < 0)
    1666           0 :                 break;
    1667          13 :             const auto psName = oTable.GetFieldValue(iName);
    1668          13 :             if (psName && psName->String == name)
    1669             :             {
    1670           1 :                 const auto psType = oTable.GetFieldValue(iType);
    1671           1 :                 if (psType && (EQUAL(psType->String, pszRangeDomainTypeUUID) ||
    1672           1 :                                EQUAL(psType->String, pszCodedDomainTypeUUID)))
    1673             :                 {
    1674           1 :                     const auto psUUID = oTable.GetFieldValue(iUUID);
    1675           1 :                     if (psUUID)
    1676             :                     {
    1677           1 :                         osUUID = psUUID->String;
    1678             :                     }
    1679             : 
    1680           1 :                     if (!(oTable.DeleteFeature(iCurFeat + 1) && oTable.Sync()))
    1681             :                     {
    1682           0 :                         return false;
    1683             :                     }
    1684           1 :                     break;
    1685             :                 }
    1686             :             }
    1687             :         }
    1688             :     }
    1689           2 :     if (osUUID.empty())
    1690           1 :         return false;
    1691             : 
    1692             :     // Remove links from layers to domain, into GDB_ItemRelationships
    1693             :     {
    1694           1 :         FileGDBTable oTable;
    1695           1 :         if (!oTable.Open(m_osGDBItemRelationshipsFilename.c_str(), true))
    1696           0 :             return false;
    1697             : 
    1698           1 :         FETCH_FIELD_IDX(iDestID, "DestID", FGFT_GUID);
    1699           1 :         FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
    1700             : 
    1701           5 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    1702             :              ++iCurFeat)
    1703             :         {
    1704           4 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    1705           4 :             if (iCurFeat < 0)
    1706           0 :                 break;
    1707             : 
    1708           4 :             const auto psType = oTable.GetFieldValue(iType);
    1709           4 :             if (psType && EQUAL(psType->String, pszDomainInDatasetUUID))
    1710             :             {
    1711           3 :                 const auto psDestID = oTable.GetFieldValue(iDestID);
    1712           3 :                 if (psDestID && EQUAL(psDestID->String, osUUID.c_str()))
    1713             :                 {
    1714           0 :                     if (!(oTable.DeleteFeature(iCurFeat + 1) && oTable.Sync()))
    1715             :                     {
    1716           0 :                         return false;
    1717             :                     }
    1718             :                 }
    1719             :             }
    1720             :         }
    1721             : 
    1722           1 :         if (!oTable.Sync())
    1723             :         {
    1724           0 :             return false;
    1725             :         }
    1726             :     }
    1727             : 
    1728           1 :     m_oMapFieldDomains.erase(name);
    1729             : 
    1730           1 :     return true;
    1731             : }
    1732             : 
    1733             : /************************************************************************/
    1734             : /*                        UpdateFieldDomain()                           */
    1735             : /************************************************************************/
    1736             : 
    1737           1 : bool OGROpenFileGDBDataSource::UpdateFieldDomain(
    1738             :     std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
    1739             : {
    1740           2 :     const std::string domainName(domain->GetName());
    1741           1 :     if (eAccess != GA_Update)
    1742             :     {
    1743           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1744             :                  "UpdateFieldDomain() not supported on read-only dataset");
    1745           0 :         return false;
    1746             :     }
    1747             : 
    1748           1 :     if (GetFieldDomain(domainName) == nullptr)
    1749             :     {
    1750           0 :         failureReason = "The domain should already exist to be updated";
    1751           0 :         return false;
    1752             :     }
    1753             : 
    1754           1 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    1755           0 :         return false;
    1756             : 
    1757             :     std::string osXML =
    1758           2 :         BuildXMLFieldDomainDef(domain.get(), false, failureReason);
    1759           1 :     if (osXML.empty())
    1760             :     {
    1761           0 :         return false;
    1762             :     }
    1763             : 
    1764           2 :     FileGDBTable oTable;
    1765           1 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
    1766           0 :         return false;
    1767             : 
    1768           1 :     FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
    1769           1 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
    1770           1 :     FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
    1771             : 
    1772           1 :     bool bMatchFound = false;
    1773           3 :     for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    1774             :          ++iCurFeat)
    1775             :     {
    1776           3 :         iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    1777           3 :         if (iCurFeat < 0)
    1778           0 :             break;
    1779           3 :         const auto psName = oTable.GetFieldValue(iName);
    1780           3 :         if (psName && psName->String == domainName)
    1781             :         {
    1782           1 :             const auto psType = oTable.GetFieldValue(iType);
    1783           1 :             if (psType && (EQUAL(psType->String, pszRangeDomainTypeUUID) ||
    1784           0 :                            EQUAL(psType->String, pszCodedDomainTypeUUID)))
    1785             :             {
    1786           1 :                 auto asFields = oTable.GetAllFieldValues();
    1787             : 
    1788           2 :                 if (!OGR_RawField_IsNull(&asFields[iDefinition]) &&
    1789           1 :                     !OGR_RawField_IsUnset(&asFields[iDefinition]))
    1790             :                 {
    1791           1 :                     CPLFree(asFields[iDefinition].String);
    1792             :                 }
    1793           1 :                 asFields[iDefinition].String = CPLStrdup(osXML.c_str());
    1794             : 
    1795           1 :                 const char *pszNewTypeUUID = "";
    1796           1 :                 switch (domain->GetDomainType())
    1797             :                 {
    1798           0 :                     case OFDT_CODED:
    1799           0 :                         pszNewTypeUUID = pszCodedDomainTypeUUID;
    1800           0 :                         break;
    1801             : 
    1802           1 :                     case OFDT_RANGE:
    1803           1 :                         pszNewTypeUUID = pszRangeDomainTypeUUID;
    1804           1 :                         break;
    1805             : 
    1806           0 :                     case OFDT_GLOB:
    1807           0 :                         CPLAssert(false);
    1808             :                         break;
    1809             :                 }
    1810             : 
    1811           2 :                 if (!OGR_RawField_IsNull(&asFields[iType]) &&
    1812           1 :                     !OGR_RawField_IsUnset(&asFields[iType]))
    1813             :                 {
    1814           1 :                     CPLFree(asFields[iType].String);
    1815             :                 }
    1816           1 :                 asFields[iType].String = CPLStrdup(pszNewTypeUUID);
    1817             : 
    1818             :                 bool bRet =
    1819           1 :                     oTable.UpdateFeature(iCurFeat + 1, asFields, nullptr);
    1820           1 :                 oTable.FreeAllFieldValues(asFields);
    1821           1 :                 if (!bRet)
    1822           0 :                     return false;
    1823           1 :                 bMatchFound = true;
    1824           1 :                 break;
    1825             :             }
    1826             :         }
    1827             : 
    1828           2 :         if (!oTable.Sync())
    1829             :         {
    1830           0 :             return false;
    1831             :         }
    1832             :     }
    1833             : 
    1834           1 :     if (!bMatchFound)
    1835           0 :         return false;
    1836             : 
    1837           1 :     m_oMapFieldDomains[domainName] = std::move(domain);
    1838             : 
    1839           1 :     return true;
    1840             : }
    1841             : 
    1842             : /************************************************************************/
    1843             : /*                        GetRelationshipNames()                        */
    1844             : /************************************************************************/
    1845             : 
    1846          17 : std::vector<std::string> OGROpenFileGDBDataSource::GetRelationshipNames(
    1847             :     CPL_UNUSED CSLConstList papszOptions) const
    1848             : 
    1849             : {
    1850          17 :     std::vector<std::string> oasNames;
    1851          17 :     oasNames.reserve(m_osMapRelationships.size());
    1852          77 :     for (auto it = m_osMapRelationships.begin();
    1853         137 :          it != m_osMapRelationships.end(); ++it)
    1854             :     {
    1855          60 :         oasNames.emplace_back(it->first);
    1856             :     }
    1857          17 :     return oasNames;
    1858             : }
    1859             : 
    1860             : /************************************************************************/
    1861             : /*                        GetRelationship()                             */
    1862             : /************************************************************************/
    1863             : 
    1864             : const GDALRelationship *
    1865          46 : OGROpenFileGDBDataSource::GetRelationship(const std::string &name) const
    1866             : 
    1867             : {
    1868          46 :     auto it = m_osMapRelationships.find(name);
    1869          46 :     if (it == m_osMapRelationships.end())
    1870           8 :         return nullptr;
    1871             : 
    1872          38 :     return it->second.get();
    1873             : }
    1874             : 
    1875             : /************************************************************************/
    1876             : /*                          AddRelationship()                           */
    1877             : /************************************************************************/
    1878             : 
    1879           7 : bool OGROpenFileGDBDataSource::AddRelationship(
    1880             :     std::unique_ptr<GDALRelationship> &&relationship,
    1881             :     std::string &failureReason)
    1882             : {
    1883          14 :     const std::string relationshipName(relationship->GetName());
    1884           7 :     if (eAccess != GA_Update)
    1885             :     {
    1886           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1887             :                  "AddRelationship() not supported on read-only dataset");
    1888           0 :         return false;
    1889             :     }
    1890             : 
    1891           7 :     if (GetRelationship(relationshipName) != nullptr)
    1892             :     {
    1893           1 :         failureReason = "A relationship of identical name already exists";
    1894           1 :         return false;
    1895             :     }
    1896             : 
    1897           6 :     if (relationship->GetCardinality() ==
    1898             :         GDALRelationshipCardinality::GRC_MANY_TO_ONE)
    1899             :     {
    1900           0 :         failureReason = "Many to one relationships are not supported";
    1901           0 :         return false;
    1902             :     }
    1903           6 :     else if (relationship->GetCardinality() ==
    1904           3 :                  GDALRelationshipCardinality::GRC_MANY_TO_MANY &&
    1905           8 :              !relationship->GetMappingTableName().empty() &&
    1906           2 :              relationship->GetName() != relationship->GetMappingTableName())
    1907             :     {
    1908             :         failureReason = "Mapping table name must match relationship name for "
    1909           1 :                         "many-to-many relationships";
    1910           1 :         return false;
    1911             :     }
    1912             : 
    1913           5 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    1914           0 :         return false;
    1915             : 
    1916          10 :     const std::string osThisGUID = OFGDBGenerateUUID();
    1917             : 
    1918          10 :     FileGDBTable oTable;
    1919          10 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true) ||
    1920           5 :         oTable.GetTotalRecordCount() >= INT32_MAX)
    1921           0 :         return false;
    1922             : 
    1923             :     // hopefully this just needs to be a unique value. Seems to autoincrement
    1924             :     // when created from ArcMap at least!
    1925           5 :     const int iDsId = static_cast<int>(oTable.GetTotalRecordCount() + 1);
    1926             : 
    1927          10 :     std::string osMappingTableOidName;
    1928           5 :     if (relationship->GetCardinality() ==
    1929             :         GDALRelationshipCardinality::GRC_MANY_TO_MANY)
    1930             :     {
    1931           2 :         if (!relationship->GetMappingTableName().empty())
    1932             :         {
    1933             :             auto poLayer =
    1934           1 :                 GetLayerByName(relationship->GetMappingTableName().c_str());
    1935           1 :             if (poLayer)
    1936             :             {
    1937           1 :                 osMappingTableOidName = poLayer->GetFIDColumn();
    1938             :             }
    1939             :         }
    1940             :         else
    1941             :         {
    1942             :             // auto create mapping table
    1943           1 :             CPLStringList aosOptions;
    1944           1 :             aosOptions.SetNameValue("FID", "RID");
    1945           1 :             OGRLayer *poMappingTable = ICreateLayer(
    1946           1 :                 relationship->GetName().c_str(), nullptr, aosOptions.List());
    1947           1 :             if (!poMappingTable)
    1948             :             {
    1949           0 :                 failureReason = "Could not create mapping table " +
    1950           0 :                                 relationship->GetMappingTableName();
    1951           0 :                 return false;
    1952             :             }
    1953             : 
    1954           1 :             OGRFieldDefn oOriginFkFieldDefn("origin_fk", OFTString);
    1955           1 :             if (poMappingTable->CreateField(&oOriginFkFieldDefn) != OGRERR_NONE)
    1956             :             {
    1957             :                 failureReason =
    1958           0 :                     "Could not create origin_fk field in mapping table " +
    1959           0 :                     relationship->GetMappingTableName();
    1960           0 :                 return false;
    1961             :             }
    1962             : 
    1963           1 :             OGRFieldDefn oDestinationFkFieldDefn("destination_fk", OFTString);
    1964           1 :             if (poMappingTable->CreateField(&oDestinationFkFieldDefn) !=
    1965             :                 OGRERR_NONE)
    1966             :             {
    1967             :                 failureReason =
    1968           0 :                     "Could not create destination_fk field in mapping table " +
    1969           0 :                     relationship->GetMappingTableName();
    1970           0 :                 return false;
    1971             :             }
    1972             : 
    1973           1 :             osMappingTableOidName = "RID";
    1974           1 :             relationship->SetMappingTableName(relationship->GetName());
    1975           2 :             relationship->SetLeftMappingTableFields({"origin_fk"});
    1976           2 :             relationship->SetRightMappingTableFields({"destination_fk"});
    1977             :         }
    1978             :     }
    1979             : 
    1980             :     std::string osXML = BuildXMLRelationshipDef(
    1981          10 :         relationship.get(), iDsId, osMappingTableOidName, failureReason);
    1982           5 :     if (osXML.empty())
    1983             :     {
    1984           0 :         return false;
    1985             :     }
    1986             : 
    1987             :     std::string osItemInfoXML =
    1988          10 :         BuildXMLRelationshipItemInfo(relationship.get(), failureReason);
    1989           5 :     if (osItemInfoXML.empty())
    1990             :     {
    1991           0 :         return false;
    1992             :     }
    1993             : 
    1994             :     std::string osDocumentationXML =
    1995          10 :         BuildXMLRelationshipDocumentation(relationship.get(), failureReason);
    1996           5 :     if (osDocumentationXML.empty())
    1997             :     {
    1998           0 :         return false;
    1999             :     }
    2000             : 
    2001          10 :     std::string osOriginUUID;
    2002           5 :     if (!FindUUIDFromName(relationship->GetLeftTableName(), osOriginUUID))
    2003             :     {
    2004           2 :         failureReason = ("Left table " + relationship->GetLeftTableName() +
    2005             :                          " is not an existing layer in the dataset")
    2006           1 :                             .c_str();
    2007           1 :         return false;
    2008             :     }
    2009           8 :     std::string osDestinationUUID;
    2010           4 :     if (!FindUUIDFromName(relationship->GetRightTableName(), osDestinationUUID))
    2011             :     {
    2012           0 :         failureReason = ("Right table " + relationship->GetRightTableName() +
    2013             :                          " is not an existing layer in the dataset")
    2014           0 :                             .c_str();
    2015           0 :         return false;
    2016             :     }
    2017             : 
    2018           4 :     FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
    2019           4 :     FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
    2020           4 :     FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
    2021           4 :     FETCH_FIELD_IDX(iPhysicalName, "PhysicalName", FGFT_STRING);
    2022           4 :     FETCH_FIELD_IDX(iPath, "Path", FGFT_STRING);
    2023           4 :     FETCH_FIELD_IDX(iDatasetSubtype1, "DatasetSubtype1", FGFT_INT32);
    2024           4 :     FETCH_FIELD_IDX(iDatasetSubtype2, "DatasetSubtype2", FGFT_INT32);
    2025           4 :     FETCH_FIELD_IDX(iURL, "URL", FGFT_STRING);
    2026           4 :     FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
    2027           4 :     FETCH_FIELD_IDX(iDocumentation, "Documentation", FGFT_XML);
    2028           4 :     FETCH_FIELD_IDX(iItemInfo, "ItemInfo", FGFT_XML);
    2029           4 :     FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
    2030             : 
    2031           4 :     std::vector<OGRField> fields(oTable.GetFieldCount(),
    2032          12 :                                  FileGDBField::UNSET_FIELD);
    2033           4 :     fields[iUUID].String = const_cast<char *>(osThisGUID.c_str());
    2034           4 :     fields[iType].String = const_cast<char *>(pszRelationshipTypeUUID);
    2035           4 :     fields[iName].String = const_cast<char *>(relationshipName.c_str());
    2036           8 :     CPLString osUCName(relationshipName);
    2037           4 :     osUCName.toupper();
    2038           4 :     fields[iPhysicalName].String = const_cast<char *>(osUCName.c_str());
    2039           8 :     const std::string osPath = "\\" + relationshipName;
    2040           4 :     fields[iPath].String = const_cast<char *>(osPath.c_str());
    2041           4 :     switch (relationship->GetCardinality())
    2042             :     {
    2043           1 :         case GDALRelationshipCardinality::GRC_ONE_TO_ONE:
    2044           1 :             fields[iDatasetSubtype1].Integer = 1;
    2045           1 :             break;
    2046           1 :         case GDALRelationshipCardinality::GRC_ONE_TO_MANY:
    2047           1 :             fields[iDatasetSubtype1].Integer = 2;
    2048           1 :             break;
    2049           2 :         case GDALRelationshipCardinality::GRC_MANY_TO_MANY:
    2050           2 :             fields[iDatasetSubtype1].Integer = 3;
    2051           2 :             break;
    2052           0 :         case GDALRelationshipCardinality::GRC_MANY_TO_ONE:
    2053             :             // unreachable
    2054           0 :             break;
    2055             :     }
    2056           4 :     fields[iDatasetSubtype2].Integer = 0;
    2057           4 :     fields[iURL].String = const_cast<char *>("");
    2058           4 :     fields[iDefinition].String = const_cast<char *>(osXML.c_str());
    2059           4 :     fields[iDocumentation].String =
    2060           4 :         const_cast<char *>(osDocumentationXML.c_str());
    2061           4 :     fields[iItemInfo].String = const_cast<char *>(osItemInfoXML.c_str());
    2062           4 :     fields[iProperties].Integer = 1;
    2063             : 
    2064           4 :     if (!(oTable.CreateFeature(fields, nullptr) && oTable.Sync()))
    2065           0 :         return false;
    2066             : 
    2067           4 :     if (!RegisterRelationshipInItemRelationships(osThisGUID, osOriginUUID,
    2068             :                                                  osDestinationUUID))
    2069           0 :         return false;
    2070             : 
    2071           4 :     m_osMapRelationships[relationshipName] = std::move(relationship);
    2072             : 
    2073           4 :     return true;
    2074             : }
    2075             : 
    2076             : /************************************************************************/
    2077             : /*                         DeleteRelationship()                         */
    2078             : /************************************************************************/
    2079             : 
    2080           2 : bool OGROpenFileGDBDataSource::DeleteRelationship(const std::string &name,
    2081             :                                                   std::string &failureReason)
    2082             : {
    2083           2 :     if (eAccess != GA_Update)
    2084             :     {
    2085           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2086             :                  "DeleteRelationship() not supported on read-only dataset");
    2087           0 :         return false;
    2088             :     }
    2089             : 
    2090           2 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    2091           0 :         return false;
    2092             : 
    2093             :     // Remove from GDB_Items
    2094           4 :     std::string osUUID;
    2095             :     {
    2096           2 :         FileGDBTable oTable;
    2097           2 :         if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
    2098           0 :             return false;
    2099             : 
    2100           2 :         FETCH_FIELD_IDX_WITH_RET(iUUID, "UUID", FGFT_GLOBALID, false);
    2101           2 :         FETCH_FIELD_IDX_WITH_RET(iType, "Type", FGFT_GUID, false);
    2102           2 :         FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, false);
    2103             : 
    2104          36 :         for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    2105             :              ++iCurFeat)
    2106             :         {
    2107          34 :             iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    2108          34 :             if (iCurFeat < 0)
    2109           0 :                 break;
    2110             : 
    2111          34 :             const auto psType = oTable.GetFieldValue(iType);
    2112          34 :             if (!psType || !EQUAL(psType->String, pszRelationshipTypeUUID))
    2113             :             {
    2114          26 :                 continue;
    2115             :             }
    2116             : 
    2117           8 :             const auto psName = oTable.GetFieldValue(iName);
    2118           8 :             if (psName && strcmp(psName->String, name.c_str()) != 0)
    2119             :             {
    2120           7 :                 continue;
    2121             :             }
    2122             : 
    2123           1 :             const auto psUUID = oTable.GetFieldValue(iUUID);
    2124           1 :             if (psUUID)
    2125             :             {
    2126           1 :                 osUUID = psUUID->String;
    2127           1 :                 if (!(oTable.DeleteFeature(iCurFeat + 1) && oTable.Sync()))
    2128             :                 {
    2129             :                     failureReason =
    2130           0 :                         "Could not delete relationship from GDB_Items table";
    2131           0 :                     return false;
    2132             :                 }
    2133             :             }
    2134             :         }
    2135             :     }
    2136             : 
    2137           2 :     if (osUUID.empty())
    2138             :     {
    2139           1 :         failureReason = "Could not find relationship with name " + name;
    2140           1 :         return false;
    2141             :     }
    2142             : 
    2143           1 :     if (!RemoveRelationshipFromItemRelationships(osUUID))
    2144             :     {
    2145             :         failureReason =
    2146           0 :             "Could not remove relationship from GDB_ItemRelationships";
    2147           0 :         return false;
    2148             :     }
    2149             : 
    2150           1 :     m_osMapRelationships.erase(name);
    2151           1 :     return true;
    2152             : }
    2153             : 
    2154             : /************************************************************************/
    2155             : /*                        UpdateRelationship()                          */
    2156             : /************************************************************************/
    2157             : 
    2158           3 : bool OGROpenFileGDBDataSource::UpdateRelationship(
    2159             :     std::unique_ptr<GDALRelationship> &&relationship,
    2160             :     std::string &failureReason)
    2161             : {
    2162           6 :     const std::string relationshipName(relationship->GetName());
    2163           3 :     if (eAccess != GA_Update)
    2164             :     {
    2165           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2166             :                  "UpdateRelationship() not supported on read-only dataset");
    2167           0 :         return false;
    2168             :     }
    2169             : 
    2170           3 :     if (GetRelationship(relationshipName) == nullptr)
    2171             :     {
    2172           1 :         failureReason = "The relationship should already exist to be updated";
    2173           1 :         return false;
    2174             :     }
    2175             : 
    2176           2 :     if (relationship->GetCardinality() ==
    2177             :         GDALRelationshipCardinality::GRC_MANY_TO_ONE)
    2178             :     {
    2179           0 :         failureReason = "Many to one relationships are not supported";
    2180           0 :         return false;
    2181             :     }
    2182             : 
    2183           2 :     if (m_bInTransaction && !BackupSystemTablesForTransaction())
    2184           0 :         return false;
    2185             : 
    2186           4 :     std::string osOriginUUID;
    2187           2 :     if (!FindUUIDFromName(relationship->GetLeftTableName(), osOriginUUID))
    2188             :     {
    2189           0 :         failureReason = ("Left table " + relationship->GetLeftTableName() +
    2190             :                          " is not an existing layer in the dataset")
    2191           0 :                             .c_str();
    2192           0 :         return false;
    2193             :     }
    2194           4 :     std::string osDestinationUUID;
    2195           2 :     if (!FindUUIDFromName(relationship->GetRightTableName(), osDestinationUUID))
    2196             :     {
    2197           0 :         failureReason = ("Right table " + relationship->GetRightTableName() +
    2198             :                          " is not an existing layer in the dataset")
    2199           0 :                             .c_str();
    2200           0 :         return false;
    2201             :     }
    2202             : 
    2203           4 :     FileGDBTable oTable;
    2204           4 :     if (!oTable.Open(m_osGDBItemsFilename.c_str(), true) ||
    2205           2 :         oTable.GetTotalRecordCount() >= INT32_MAX)
    2206             :     {
    2207           0 :         return false;
    2208             :     }
    2209             : 
    2210             :     // hopefully this just needs to be a unique value. Seems to autoincrement
    2211             :     // when created from ArcMap at least!
    2212           2 :     const int iDsId = static_cast<int>(oTable.GetTotalRecordCount()) + 1;
    2213             : 
    2214           4 :     std::string osMappingTableOidName;
    2215           2 :     if (relationship->GetCardinality() ==
    2216             :         GDALRelationshipCardinality::GRC_MANY_TO_MANY)
    2217             :     {
    2218           0 :         if (!relationship->GetMappingTableName().empty())
    2219             :         {
    2220             :             auto poLayer =
    2221           0 :                 GetLayerByName(relationship->GetMappingTableName().c_str());
    2222           0 :             if (poLayer)
    2223             :             {
    2224           0 :                 osMappingTableOidName = poLayer->GetFIDColumn();
    2225             :             }
    2226             :         }
    2227           0 :         if (osMappingTableOidName.empty())
    2228             :         {
    2229           0 :             failureReason = "Relationship mapping table does not exist";
    2230           0 :             return false;
    2231             :         }
    2232             :     }
    2233             : 
    2234             :     std::string osXML = BuildXMLRelationshipDef(
    2235           4 :         relationship.get(), iDsId, osMappingTableOidName, failureReason);
    2236           2 :     if (osXML.empty())
    2237             :     {
    2238           0 :         return false;
    2239             :     }
    2240             : 
    2241           2 :     FETCH_FIELD_IDX_WITH_RET(iUUID, "UUID", FGFT_GLOBALID, false);
    2242           2 :     FETCH_FIELD_IDX_WITH_RET(iType, "Type", FGFT_GUID, false);
    2243           2 :     FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, false);
    2244           2 :     FETCH_FIELD_IDX_WITH_RET(iDefinition, "Definition", FGFT_XML, false);
    2245           2 :     FETCH_FIELD_IDX_WITH_RET(iDatasetSubtype1, "DatasetSubtype1", FGFT_INT32,
    2246             :                              false);
    2247             : 
    2248           2 :     bool bMatchFound = false;
    2249           4 :     std::string osUUID;
    2250          16 :     for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
    2251             :          ++iCurFeat)
    2252             :     {
    2253          16 :         iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
    2254          16 :         if (iCurFeat < 0)
    2255           0 :             break;
    2256          16 :         const auto psName = oTable.GetFieldValue(iName);
    2257          16 :         if (psName && psName->String == relationshipName)
    2258             :         {
    2259           2 :             const auto psType = oTable.GetFieldValue(iType);
    2260           2 :             if (psType && EQUAL(psType->String, pszRelationshipTypeUUID))
    2261             :             {
    2262           2 :                 const auto psUUID = oTable.GetFieldValue(iUUID);
    2263           2 :                 if (psUUID)
    2264             :                 {
    2265           2 :                     osUUID = psUUID->String;
    2266             :                 }
    2267             : 
    2268           2 :                 auto asFields = oTable.GetAllFieldValues();
    2269             : 
    2270           4 :                 if (!OGR_RawField_IsNull(&asFields[iDefinition]) &&
    2271           2 :                     !OGR_RawField_IsUnset(&asFields[iDefinition]))
    2272             :                 {
    2273           2 :                     CPLFree(asFields[iDefinition].String);
    2274             :                 }
    2275           2 :                 asFields[iDefinition].String = CPLStrdup(osXML.c_str());
    2276             : 
    2277           2 :                 switch (relationship->GetCardinality())
    2278             :                 {
    2279           0 :                     case GDALRelationshipCardinality::GRC_ONE_TO_ONE:
    2280           0 :                         asFields[iDatasetSubtype1].Integer = 1;
    2281           0 :                         break;
    2282           2 :                     case GDALRelationshipCardinality::GRC_ONE_TO_MANY:
    2283           2 :                         asFields[iDatasetSubtype1].Integer = 2;
    2284           2 :                         break;
    2285           0 :                     case GDALRelationshipCardinality::GRC_MANY_TO_MANY:
    2286           0 :                         asFields[iDatasetSubtype1].Integer = 3;
    2287           0 :                         break;
    2288           0 :                     case GDALRelationshipCardinality::GRC_MANY_TO_ONE:
    2289             :                         // unreachable
    2290           0 :                         break;
    2291             :                 }
    2292             : 
    2293             :                 bool bRet =
    2294           2 :                     oTable.UpdateFeature(iCurFeat + 1, asFields, nullptr);
    2295           2 :                 oTable.FreeAllFieldValues(asFields);
    2296           2 :                 if (!bRet)
    2297           0 :                     return false;
    2298           2 :                 bMatchFound = true;
    2299           2 :                 break;
    2300             :             }
    2301             :         }
    2302             : 
    2303          14 :         if (!oTable.Sync())
    2304             :         {
    2305           0 :             return false;
    2306             :         }
    2307             :     }
    2308             : 
    2309           2 :     if (!bMatchFound)
    2310           0 :         return false;
    2311             : 
    2312             :     // First delete all existing item relationships for the item, and then we'll
    2313             :     // rebuild them again.
    2314           2 :     if (!RemoveRelationshipFromItemRelationships(osUUID))
    2315             :     {
    2316             :         failureReason =
    2317           0 :             "Could not remove relationship from GDB_ItemRelationships";
    2318           0 :         return false;
    2319             :     }
    2320           2 :     if (!RegisterRelationshipInItemRelationships(osUUID, osOriginUUID,
    2321             :                                                  osDestinationUUID))
    2322             :     {
    2323             :         failureReason =
    2324           0 :             "Could not register relationship in GDB_ItemRelationships";
    2325           0 :         return false;
    2326             :     }
    2327             : 
    2328           2 :     m_osMapRelationships[relationshipName] = std::move(relationship);
    2329             : 
    2330           2 :     return true;
    2331             : }
    2332             : 
    2333             : /************************************************************************/
    2334             : /*                        StartTransaction()                            */
    2335             : /************************************************************************/
    2336             : 
    2337          16 : OGRErr OGROpenFileGDBDataSource::StartTransaction(int bForce)
    2338             : {
    2339          16 :     if (!bForce)
    2340             :     {
    2341           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    2342             :                  "Transactions only supported in forced mode");
    2343           0 :         return OGRERR_UNSUPPORTED_OPERATION;
    2344             :     }
    2345             : 
    2346          16 :     if (eAccess != GA_Update)
    2347           1 :         return OGRERR_FAILURE;
    2348             : 
    2349          15 :     if (m_bInTransaction)
    2350             :     {
    2351           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    2352             :                  "Transaction is already in progress");
    2353           1 :         return OGRERR_FAILURE;
    2354             :     }
    2355             : 
    2356             :     m_osTransactionBackupDirname =
    2357          14 :         CPLFormFilename(m_osDirName.c_str(), ".ogrtransaction_backup", nullptr);
    2358             :     VSIStatBufL sStat;
    2359          14 :     if (VSIStatL(m_osTransactionBackupDirname.c_str(), &sStat) == 0)
    2360             :     {
    2361           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    2362             :                  "A previous backup directory %s already exists, which means "
    2363             :                  "that a previous transaction was not cleanly committed or "
    2364             :                  "rolled back.\n"
    2365             :                  "Either manually restore the previous state from that "
    2366             :                  "directory or remove it, before creating a new transaction.",
    2367             :                  m_osTransactionBackupDirname.c_str());
    2368           1 :         return OGRERR_FAILURE;
    2369             :     }
    2370          13 :     else if (VSIMkdir(m_osTransactionBackupDirname.c_str(), 0755) != 0)
    2371             :     {
    2372           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot create directory %s",
    2373             :                  m_osTransactionBackupDirname.c_str());
    2374           0 :         return OGRERR_FAILURE;
    2375             :     }
    2376             : 
    2377          13 :     m_bInTransaction = true;
    2378          13 :     return OGRERR_NONE;
    2379             : }
    2380             : 
    2381             : /************************************************************************/
    2382             : /*                   BackupSystemTablesForTransaction()                 */
    2383             : /************************************************************************/
    2384             : 
    2385          10 : bool OGROpenFileGDBDataSource::BackupSystemTablesForTransaction()
    2386             : {
    2387          10 :     if (m_bSystemTablesBackedup)
    2388           5 :         return true;
    2389             : 
    2390           5 :     char **papszFiles = VSIReadDir(m_osDirName.c_str());
    2391         106 :     for (char **papszIter = papszFiles;
    2392         106 :          papszIter != nullptr && *papszIter != nullptr; ++papszIter)
    2393             :     {
    2394         101 :         const std::string osBasename = CPLGetBasename(*papszIter);
    2395         101 :         if (osBasename.size() == strlen("a00000001") &&
    2396         179 :             osBasename.compare(0, 8, "a0000000") == 0 && osBasename[8] >= '1' &&
    2397          78 :             osBasename[8] <= '8')
    2398             :         {
    2399             :             std::string osDestFilename = CPLFormFilename(
    2400          72 :                 m_osTransactionBackupDirname.c_str(), *papszIter, nullptr);
    2401             :             std::string osSourceFilename =
    2402          72 :                 CPLFormFilename(m_osDirName.c_str(), *papszIter, nullptr);
    2403          72 :             if (CPLCopyFile(osDestFilename.c_str(), osSourceFilename.c_str()) !=
    2404             :                 0)
    2405             :             {
    2406           0 :                 CSLDestroy(papszFiles);
    2407           0 :                 return false;
    2408             :             }
    2409             :         }
    2410             :     }
    2411             : 
    2412           5 :     CSLDestroy(papszFiles);
    2413           5 :     m_bSystemTablesBackedup = true;
    2414           5 :     return true;
    2415             : }
    2416             : 
    2417             : /************************************************************************/
    2418             : /*                        CommitTransaction()                           */
    2419             : /************************************************************************/
    2420             : 
    2421           5 : OGRErr OGROpenFileGDBDataSource::CommitTransaction()
    2422             : {
    2423           5 :     if (!m_bInTransaction)
    2424             :     {
    2425           1 :         CPLError(CE_Failure, CPLE_AppDefined, "No transaction in progress");
    2426           1 :         return OGRERR_FAILURE;
    2427             :     }
    2428             : 
    2429           7 :     for (auto &poLayer : m_apoLayers)
    2430           3 :         poLayer->CommitEmulatedTransaction();
    2431             : 
    2432           4 :     VSIRmdirRecursive(m_osTransactionBackupDirname.c_str());
    2433             : 
    2434           4 :     m_bInTransaction = false;
    2435           4 :     m_bSystemTablesBackedup = false;
    2436           4 :     m_oSetLayersCreatedInTransaction.clear();
    2437           4 :     m_oSetLayersDeletedInTransaction.clear();
    2438             : 
    2439           4 :     return OGRERR_NONE;
    2440             : }
    2441             : 
    2442             : /************************************************************************/
    2443             : /*                       RollbackTransaction()                          */
    2444             : /************************************************************************/
    2445             : 
    2446          11 : OGRErr OGROpenFileGDBDataSource::RollbackTransaction()
    2447             : {
    2448          11 :     if (!m_bInTransaction)
    2449             :     {
    2450           1 :         CPLError(CE_Failure, CPLE_AppDefined, "No transaction in progress");
    2451           1 :         return OGRERR_FAILURE;
    2452             :     }
    2453             : 
    2454          10 :     OGRErr eErr = OGRERR_NONE;
    2455             : 
    2456             :     // Restore system tables
    2457             :     {
    2458          10 :         char **papszFiles = VSIReadDir(m_osTransactionBackupDirname.c_str());
    2459          10 :         if (papszFiles == nullptr)
    2460             :         {
    2461           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    2462             :                      "Backup directory %s no longer found! Original database "
    2463             :                      "cannot be restored",
    2464             :                      m_osTransactionBackupDirname.c_str());
    2465           2 :             return OGRERR_FAILURE;
    2466             :         }
    2467          78 :         for (char **papszIter = papszFiles;
    2468          78 :              papszIter != nullptr && *papszIter != nullptr; ++papszIter)
    2469             :         {
    2470         140 :             const std::string osBasename = CPLGetBasename(*papszIter);
    2471          70 :             if (osBasename.size() == strlen("a00000001") &&
    2472          56 :                 osBasename.compare(0, 8, "a0000000") == 0 &&
    2473         126 :                 osBasename[8] >= '1' && osBasename[8] <= '8')
    2474             :             {
    2475             :                 std::string osDestFilename =
    2476          88 :                     CPLFormFilename(m_osDirName.c_str(), *papszIter, nullptr);
    2477             :                 std::string osSourceFilename = CPLFormFilename(
    2478          88 :                     m_osTransactionBackupDirname.c_str(), *papszIter, nullptr);
    2479          44 :                 if (CPLCopyFile(osDestFilename.c_str(),
    2480          44 :                                 osSourceFilename.c_str()) != 0)
    2481             :                 {
    2482           0 :                     eErr = OGRERR_FAILURE;
    2483             :                 }
    2484             :             }
    2485             :         }
    2486           8 :         CSLDestroy(papszFiles);
    2487             :     }
    2488             : 
    2489             :     // Restore layers in their original state
    2490          13 :     for (auto &poLayer : m_apoLayers)
    2491           5 :         poLayer->RollbackEmulatedTransaction();
    2492           9 :     for (auto &poLayer : m_oSetLayersDeletedInTransaction)
    2493           1 :         poLayer->RollbackEmulatedTransaction();
    2494             : 
    2495             :     // Remove layers created during transaction
    2496           9 :     for (auto poLayer : m_oSetLayersCreatedInTransaction)
    2497             :     {
    2498             :         const std::string osThisBasename =
    2499           2 :             CPLGetBasename(poLayer->GetFilename().c_str());
    2500           1 :         poLayer->Close();
    2501             : 
    2502           1 :         char **papszFiles = VSIReadDir(m_osDirName.c_str());
    2503          23 :         for (char **papszIter = papszFiles;
    2504          23 :              papszIter != nullptr && *papszIter != nullptr; ++papszIter)
    2505             :         {
    2506          44 :             const std::string osBasename = CPLGetBasename(*papszIter);
    2507          22 :             if (osBasename == osThisBasename)
    2508             :             {
    2509             :                 std::string osDestFilename =
    2510           6 :                     CPLFormFilename(m_osDirName.c_str(), *papszIter, nullptr);
    2511           3 :                 VSIUnlink(osDestFilename.c_str());
    2512             :             }
    2513             :         }
    2514           1 :         CSLDestroy(papszFiles);
    2515             :     }
    2516             : 
    2517           8 :     if (eErr == OGRERR_NONE)
    2518             :     {
    2519           8 :         if (VSIRmdirRecursive(m_osTransactionBackupDirname.c_str()) != 0)
    2520             :         {
    2521           0 :             CPLError(
    2522             :                 CE_Warning, CPLE_AppDefined,
    2523             :                 "Backup directory %s could not be destroyed. But original "
    2524             :                 "dataset "
    2525             :                 "should have been properly restored. You will need to manually "
    2526             :                 "remove the backup directory.",
    2527             :                 m_osTransactionBackupDirname.c_str());
    2528             :         }
    2529             :     }
    2530             :     else
    2531             :     {
    2532           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2533             :                  "Backup directory %s could not be properly restored onto "
    2534             :                  "live database. Corruption is likely!",
    2535             :                  m_osTransactionBackupDirname.c_str());
    2536             :     }
    2537             : 
    2538           8 :     m_bInTransaction = false;
    2539           8 :     m_bSystemTablesBackedup = false;
    2540           8 :     m_oSetLayersCreatedInTransaction.clear();
    2541           8 :     m_oSetLayersDeletedInTransaction.clear();
    2542             : 
    2543           8 :     return eErr;
    2544             : }

Generated by: LCOV version 1.14