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

Generated by: LCOV version 1.14