/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Implements FileGDB OGR Datasource.
 * Author:   Ragi Yaser Burhum, ragi@burhum.com
 *           Paul Ramsey, pramsey at cleverelephant.ca
 *
 ******************************************************************************
 * Copyright (c) 2010, Ragi Yaser Burhum
 * Copyright (c) 2011, Paul Ramsey <pramsey at cleverelephant.ca>
 * Copyright (c) 2011-2013, Even Rouault <even dot rouault at mines-paris dot org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "ogr_fgdb.h"
#include "cpl_conv.h"
#include "cpl_string.h"
#include "gdal.h"
#include "FGdbUtils.h"
#include "cpl_multiproc.h"

CPL_CVSID("$Id: FGdbDatasource.cpp 7e07230bbff24eb333608de4dbd460b7312839d0 2017-12-11 19:08:47Z Even Rouault $")

using std::vector;
using std::wstring;

/************************************************************************/
/*                          FGdbDataSource()                           */
/************************************************************************/

FGdbDataSource::FGdbDataSource(FGdbDriver* poDriverIn,
                               FGdbDatabaseConnection* pConnection):
OGRDataSource(),
m_poDriver(poDriverIn), m_pConnection(pConnection), m_pGeodatabase(nullptr), m_bUpdate(false),
m_poOpenFileGDBDrv(nullptr)
{
    bPerLayerCopyingForTransaction = -1;
}

/************************************************************************/
/*                          ~FGdbDataSource()                          */
/************************************************************************/

FGdbDataSource::~FGdbDataSource()
{
    CPLMutexHolderOptionalLockD(m_poDriver ? m_poDriver->GetMutex() : nullptr);

    if( m_pConnection && m_pConnection->IsLocked() )
        CommitTransaction();

    //Close();
    size_t count = m_layers.size();
    for(size_t i = 0; i < count; ++i )
    {
        m_layers[i]->CloseGDBObjects();
    }

    FixIndexes();

    if( m_poDriver )
        m_poDriver->Release( m_osPublicName );

    //size_t count = m_layers.size();
    for(size_t i = 0; i < count; ++i )
    {
        delete m_layers[i];
    }
}

/************************************************************************/
/*                             FixIndexes()                             */
/************************************************************************/

int FGdbDataSource::FixIndexes()
{
    int bRet = TRUE;
    if( m_pConnection && m_pConnection->IsFIDHackInProgress() )
    {
        m_pConnection->CloseGeodatabase();

        char* apszDrivers[2];
        apszDrivers[0] = (char*) "OpenFileGDB";
        apszDrivers[1] = nullptr;
        const char* pszSystemCatalog = CPLFormFilename(m_osFSName, "a00000001.gdbtable", nullptr);
        GDALDataset* poOpenFileGDBDS = (GDALDataset*)
            GDALOpenEx(pszSystemCatalog, GDAL_OF_VECTOR,
                       apszDrivers, nullptr, nullptr);
        if( poOpenFileGDBDS == nullptr || poOpenFileGDBDS->GetLayer(0) == nullptr )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Cannot open %s with OpenFileGDB driver. "
                     "Should not happen. Some layers will be corrupted",
                     pszSystemCatalog);
            bRet = FALSE;
        }
        else
        {
            OGRLayer* poLayer = poOpenFileGDBDS->GetLayer(0);
            size_t count = m_layers.size();
            for(size_t i = 0; i < count; ++i )
            {
                if( m_layers[i]->m_oMapOGRFIDToFGDBFID.empty())
                    continue;
                CPLString osFilter = "name = '";
                osFilter += m_layers[i]->GetName();
                osFilter += "'";
                poLayer->SetAttributeFilter(osFilter);
                poLayer->ResetReading();
                OGRFeature* poF = poLayer->GetNextFeature();
                if( poF == nullptr )
                {
                    CPLError(CE_Failure, CPLE_AppDefined,
                             "Cannot find filename for layer %s",
                             m_layers[i]->GetName());
                    bRet = FALSE;
                }
                else
                {
                    if( !m_layers[i]->EditIndexesForFIDHack(CPLFormFilename(m_osFSName,
                                        CPLSPrintf("a%08x", (int)poF->GetFID()), nullptr)) )
                    {
                        bRet = FALSE;
                    }
                }
                delete poF;
            }
        }
        GDALClose(poOpenFileGDBDS);

        m_pConnection->SetFIDHackInProgress(FALSE);
    }
    return bRet;
}

/************************************************************************/
/*                                Open()                                */
/************************************************************************/

int FGdbDataSource::Open(const char * pszNewName, int bUpdate,
                         const char* pszPublicName )
{
    m_osFSName = pszNewName;
    m_osPublicName = (pszPublicName) ? pszPublicName : pszNewName;
    m_pGeodatabase = m_pConnection->GetGDB();
    m_bUpdate = CPL_TO_BOOL(bUpdate);
    m_poOpenFileGDBDrv = (GDALDriver*) GDALGetDriverByName("OpenFileGDB");

    std::vector<std::wstring> typesRequested;

    // We're only interested in Tables, Feature Datasets and Feature Classes
    typesRequested.push_back(L"Feature Class");
    typesRequested.push_back(L"Table");
    typesRequested.push_back(L"Feature Dataset");

    bool rv = LoadLayers(L"\\");

    return rv;
}

/************************************************************************/
/*                               Close()                                */
/************************************************************************/

int FGdbDataSource::Close(int bCloseGeodatabase)
{
    size_t count = m_layers.size();
    for(size_t i = 0; i < count; ++i )
    {
        m_layers[i]->CloseGDBObjects();
    }

    int bRet = FixIndexes();
    if( m_pConnection && bCloseGeodatabase )
        m_pConnection->CloseGeodatabase();
    m_pGeodatabase = nullptr;
    return bRet;
}

/************************************************************************/
/*                               ReOpen()                               */
/************************************************************************/

int FGdbDataSource::ReOpen()
{
    CPLAssert(m_pGeodatabase == nullptr);

    if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL_REOPEN", ""), "CASE1") ||
        !m_pConnection->OpenGeodatabase(m_osFSName) )
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot reopen %s",
                 m_osFSName.c_str());
        return FALSE;
    }

    FGdbDataSource* pDS = new FGdbDataSource(m_poDriver, m_pConnection);
    if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL_REOPEN", ""), "CASE2") ||
        !pDS->Open(m_osPublicName, TRUE, m_osFSName) )
    {
        pDS->m_poDriver = nullptr;
        delete pDS;
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot reopen %s",
                 m_osFSName.c_str());
        return FALSE;
    }

    int bRet = TRUE;
    size_t count = m_layers.size();
    for(size_t i = 0; i < count; ++i )
    {
        FGdbLayer* pNewLayer = (FGdbLayer*)pDS->GetLayerByName(m_layers[i]->GetName());
        if( pNewLayer &&
            !EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL_REOPEN", ""), "CASE3") )
        {
            m_layers[i]->m_pTable = pNewLayer->m_pTable;
            pNewLayer->m_pTable = nullptr;
            m_layers[i]->m_pEnumRows = pNewLayer->m_pEnumRows;
            pNewLayer->m_pEnumRows = nullptr;
        }
        else
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot reopen %s",
                     m_layers[i]->GetName());
            bRet = FALSE;
        }
        m_layers[i]->m_oMapOGRFIDToFGDBFID.clear();
        m_layers[i]->m_oMapFGDBFIDToOGRFID.clear();
    }

    m_pGeodatabase = pDS->m_pGeodatabase;
    pDS->m_pGeodatabase = nullptr;

    pDS->m_poDriver = nullptr;
    pDS->m_pConnection = nullptr;
    delete pDS;

    return bRet;
}

/************************************************************************/
/*                          OpenFGDBTables()                            */
/************************************************************************/

bool FGdbDataSource::OpenFGDBTables(const std::wstring &type,
                                    const std::vector<std::wstring> &layers)
{
    fgdbError hr;
    for ( unsigned int i = 0; i < layers.size(); i++ )
    {
        Table* pTable = new Table;
        //CPLDebug("FGDB", "Opening %s", WStringToString(layers[i]).c_str());
        if (FAILED(hr = m_pGeodatabase->OpenTable(layers[i], *pTable)))
        {
            delete pTable;

            std::wstring fgdb_error_desc_w;
            fgdbError er;
            er = FileGDBAPI::ErrorInfo::GetErrorDescription(hr, fgdb_error_desc_w);
            const char* pszLikelyReason = "Might be due to unsupported spatial reference system. Using OpenFileGDB driver or FileGDB SDK >= 1.4 should solve it";
            if ( er == S_OK )
            {
                std::string fgdb_error_desc = WStringToString(fgdb_error_desc_w);
                if( fgdb_error_desc == "FileGDB compression is not installed." )
                {
                    pszLikelyReason = "Using FileGDB SDK 1.4 or later should solve this issue.";
                }
            }

            GDBErr(hr, "Error opening " + WStringToString(layers[i]),
                   CE_Warning,
                   (". Skipping it. " + CPLString(pszLikelyReason)).c_str());
            continue;
        }
        FGdbLayer* pLayer = new FGdbLayer();
        if (!pLayer->Initialize(this, pTable, layers[i], type))
        {
            delete pLayer;
            return GDBErr(hr, "Error initializing OGRLayer for " + WStringToString(layers[i]));
        }

        m_layers.push_back(pLayer);
    }
    return true;
}

/************************************************************************/
/*                            LoadLayers()                             */
/************************************************************************/

bool FGdbDataSource::LoadLayers(const std::wstring &root)
{
    std::vector<wstring> tables;
    std::vector<wstring> featureclasses;
    std::vector<wstring> featuredatasets;
    fgdbError hr;

    /* Find all the Tables in the root */
    if ( FAILED(hr = m_pGeodatabase->GetChildDatasets(root, L"Table", tables)) )
    {
        return GDBErr(hr, "Error reading Tables in " + WStringToString(root));
    }
    /* Open the tables we found */
    if ( !tables.empty() && ! OpenFGDBTables(L"Table", tables) )
        return false;

    /* Find all the Feature Classes in the root */
    if ( FAILED(hr = m_pGeodatabase->GetChildDatasets(root, L"Feature Class", featureclasses)) )
    {
        return GDBErr(hr, "Error reading Feature Classes in " + WStringToString(root));
    }
    /* Open the tables we found */
    if ( !featureclasses.empty() && ! OpenFGDBTables(L"Feature Class", featureclasses) )
        return false;

    /* Find all the Feature Datasets in the root */
    if ( FAILED(hr = m_pGeodatabase->GetChildDatasets(root, L"Feature Dataset", featuredatasets)) )
    {
        return GDBErr(hr, "Error reading Feature Datasets in " + WStringToString(root));
    }
    /* Look for Feature Classes inside the Feature Dataset */
    for ( unsigned int i = 0; i < featuredatasets.size(); i++ )
    {
        if ( FAILED(hr = m_pGeodatabase->GetChildDatasets(featuredatasets[i], L"Feature Class", featureclasses)) )
        {
            return GDBErr(hr, "Error reading Feature Classes in " + WStringToString(featuredatasets[i]));
        }
        if ( !featureclasses.empty() && ! OpenFGDBTables(L"Feature Class", featureclasses) )
            return false;
    }
    return true;
}

#if 0
/************************************************************************/
/*                            LoadLayersOld()                              */
/************************************************************************/

/* Old recursive LoadLayers. Removed in favor of simple one that only
   looks at FeatureClasses and Tables. */

// Flattens out hierarchical GDB structure.
bool FGdbDataSource::LoadLayersOld(const std::vector<wstring> & datasetTypes,
                                const wstring & parent)
{
    long hr = S_OK;

    // I didn't find an API to give me the type of the dataset based on name - I am *not*
    // parsing XML for something like this - in the meantime I can use this hack to see
    // if the dataset had any children whatsoever - if so, then I won't attempt to open it
    // otherwise, do attempt to do that

    bool childrenFound = false;
    bool errorsEncountered = false;

    for (size_t dsTypeIndex = 0; dsTypeIndex < datasetTypes.size(); dsTypeIndex++)
    {
        std::vector<wstring> childDatasets;
        m_pGeodatabase->GetChildDatasets( parent, datasetTypes[dsTypeIndex], childDatasets);

        if (!childDatasets.empty())
        {
            //it is a container of other datasets

            for (size_t childDatasetIndex = 0;
                 childDatasetIndex < childDatasets.size();
                 childDatasetIndex++)
            {
                childrenFound = true;

                // do something with it
                // For now, we just ignore dataset containers and only open the children
                // std::wcout << datasetTypes[dsTypeIndex] << L" " << childDatasets[childDatasetIndex] << std::endl;

                if (!LoadLayersOld(datasetTypes, childDatasets[childDatasetIndex]))
                    errorsEncountered = true;
            }
        }
    }

    //it is a full fledged dataset itself without children - open it (except the root)

    if ((!childrenFound) && parent != L"\\")
    {
        // wcout << "Opening " << parent << "...";
        Table* pTable = new Table;
        if (FAILED(hr = m_pGeodatabase->OpenTable(parent,*pTable)))
        {
            delete pTable;
            return GDBErr(hr, "Error opening " + WStringToString(parent));
        }

        FGdbLayer* pLayer = new FGdbLayer;

        //pLayer has ownership of the table pointer as soon Initialize is called
        if (!pLayer->Initialize(this, pTable, parent))
        {
            delete pLayer;

            return GDBErr(hr, "Error initializing OGRLayer for " +
                          WStringToString(parent));
        }

        m_layers.push_back(pLayer);
    }

    return !errorsEncountered;
}
#endif

/************************************************************************/
/*                            DeleteLayer()                             */
/************************************************************************/

OGRErr FGdbDataSource::DeleteLayer( int iLayer )
{
    if( !m_bUpdate || m_pGeodatabase == nullptr )
        return OGRERR_FAILURE;

    if( iLayer < 0 || iLayer >= static_cast<int>(m_layers.size()) )
        return OGRERR_FAILURE;

    FGdbLayer* poBaseLayer = m_layers[iLayer];

    // Fetch FGDBAPI Table before deleting OGR layer object

    //Table* pTable = poBaseLayer->GetTable();

    std::string name = poBaseLayer->GetLayerDefn()->GetName();
    std::wstring strPath = poBaseLayer->GetTablePath();
    std::wstring strType = poBaseLayer->GetType();

    // delete OGR layer
    delete m_layers[iLayer];

    //pTable = NULL; // OGR Layer had ownership of FGDB Table

    m_layers.erase(m_layers.begin() + iLayer);

    long hr;

    if (FAILED(hr = m_pGeodatabase->Delete(strPath, strType)))
    {
        CPLError( CE_Warning, CPLE_AppDefined,
                 "%s was not deleted however it has been closed", name.c_str());
        GDBErr(hr, "Failed deleting dataset");
        return OGRERR_FAILURE;
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                           TestCapability()                           */
/************************************************************************/

int FGdbDataSource::TestCapability( const char * pszCap )
{
    if( EQUAL(pszCap,ODsCCreateLayer) )
        return m_bUpdate;

    else if( EQUAL(pszCap,ODsCDeleteLayer) )
        return m_bUpdate;
    else if( EQUAL(pszCap,ODsCRandomLayerWrite) )
        return m_bUpdate;

    return FALSE;
}

/************************************************************************/
/*                              GetLayer()                              */
/************************************************************************/

OGRLayer *FGdbDataSource::GetLayer( int iLayer )
{
    int count = static_cast<int>(m_layers.size());

    if( iLayer < 0 || iLayer >= count )
        return nullptr;
    else
        return m_layers[iLayer];
}

/************************************************************************/
/*                             ICreateLayer()                           */
/*                                                                      */
/* See FGdbLayer::Create for creation options                           */
/************************************************************************/

OGRLayer *
FGdbDataSource::ICreateLayer( const char * pszLayerName,
                              OGRSpatialReference *poSRS,
                              OGRwkbGeometryType eType,
                              char ** papszOptions )
{
    if( !m_bUpdate || m_pGeodatabase == nullptr )
        return nullptr;

    FGdbLayer* pLayer = new FGdbLayer();
    if (!pLayer->Create(this, pszLayerName, poSRS, eType, papszOptions))
    {
        delete pLayer;
        return nullptr;
    }

    m_layers.push_back(pLayer);

    return pLayer;
}

/************************************************************************/
/*                   OGRFGdbSingleFeatureLayer                          */
/************************************************************************/

class OGRFGdbSingleFeatureLayer : public OGRLayer
{
  private:
    char               *pszVal;
    OGRFeatureDefn     *poFeatureDefn;
    int                 iNextShapeId;

  public:
                        OGRFGdbSingleFeatureLayer( const char* pszLayerName,
                                                   const char *pszVal );
               virtual ~OGRFGdbSingleFeatureLayer();

    virtual void        ResetReading() override { iNextShapeId = 0; }
    virtual OGRFeature *GetNextFeature() override;
    virtual OGRFeatureDefn *GetLayerDefn() override { return poFeatureDefn; }
    virtual int         TestCapability( const char * ) override { return FALSE; }
};

/************************************************************************/
/*                    OGRFGdbSingleFeatureLayer()                       */
/************************************************************************/

OGRFGdbSingleFeatureLayer::OGRFGdbSingleFeatureLayer(const char* pszLayerName,
                                                     const char *pszValIn )
{
    poFeatureDefn = new OGRFeatureDefn( pszLayerName );
    SetDescription( poFeatureDefn->GetName() );
    poFeatureDefn->Reference();
    OGRFieldDefn oField( "FIELD_1", OFTString );
    poFeatureDefn->AddFieldDefn( &oField );

    iNextShapeId = 0;
    pszVal = pszValIn ? CPLStrdup(pszValIn) : nullptr;
}

/************************************************************************/
/*                   ~OGRFGdbSingleFeatureLayer()                       */
/************************************************************************/

OGRFGdbSingleFeatureLayer::~OGRFGdbSingleFeatureLayer()
{
    if( poFeatureDefn != nullptr )
        poFeatureDefn->Release();
    CPLFree(pszVal);
}

/************************************************************************/
/*                           GetNextFeature()                           */
/************************************************************************/

OGRFeature * OGRFGdbSingleFeatureLayer::GetNextFeature()
{
    if (iNextShapeId != 0)
        return nullptr;

    OGRFeature* poFeature = new OGRFeature(poFeatureDefn);
    if (pszVal)
        poFeature->SetField(0, pszVal);
    poFeature->SetFID(iNextShapeId ++);
    return poFeature;
}

/************************************************************************/
/*                              ExecuteSQL()                            */
/************************************************************************/

OGRLayer * FGdbDataSource::ExecuteSQL( const char *pszSQLCommand,
                                       OGRGeometry *poSpatialFilter,
                                       const char *pszDialect )

{
    if( m_pConnection && m_pConnection->IsFIDHackInProgress() )
    {
        if( Close() )
            ReOpen();
    }
    if( m_pGeodatabase == nullptr )
         return nullptr;

    size_t count = m_layers.size();
    for(size_t i = 0; i < count; ++i )
    {
        m_layers[i]->EndBulkLoad();
    }

/* -------------------------------------------------------------------- */
/*      Use generic implementation for recognized dialects              */
/* -------------------------------------------------------------------- */
    if( IsGenericSQLDialect(pszDialect) )
        return OGRDataSource::ExecuteSQL( pszSQLCommand,
                                          poSpatialFilter,
                                          pszDialect );

/* -------------------------------------------------------------------- */
/*      Special case GetLayerDefinition                                 */
/* -------------------------------------------------------------------- */
    if (STARTS_WITH_CI(pszSQLCommand, "GetLayerDefinition "))
    {
        FGdbLayer* poLayer = (FGdbLayer*) GetLayerByName(pszSQLCommand + strlen("GetLayerDefinition "));
        if (poLayer)
        {
            char* pszVal = nullptr;
            poLayer->GetLayerXML(&pszVal);
            OGRLayer* poRet = new OGRFGdbSingleFeatureLayer( "LayerDefinition", pszVal );
            CPLFree(pszVal);
            return poRet;
        }
        else
            return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Special case GetLayerMetadata                                   */
/* -------------------------------------------------------------------- */
    if (STARTS_WITH_CI(pszSQLCommand, "GetLayerMetadata "))
    {
        FGdbLayer* poLayer = (FGdbLayer*) GetLayerByName(pszSQLCommand + strlen("GetLayerMetadata "));
        if (poLayer)
        {
            char* pszVal = nullptr;
            poLayer->GetLayerMetadataXML(&pszVal);
            OGRLayer* poRet = new OGRFGdbSingleFeatureLayer( "LayerMetadata", pszVal );
            CPLFree(pszVal);
            return poRet;
        }
        else
            return nullptr;
    }

    /* TODO: remove that workaround when the SDK has finally a decent */
    /* SQL support ! */
    if( STARTS_WITH_CI(pszSQLCommand, "SELECT ") && pszDialect == nullptr )
    {
        CPLDebug("FGDB", "Support for SELECT is known to be partially "
                         "non-compliant with FileGDB SDK API v1.2.\n"
                         "So for now, we use default OGR SQL engine. "
                         "Explicitly specify -dialect FileGDB\n"
                         "to use the SQL engine from the FileGDB SDK API");
        OGRLayer* poLayer = OGRDataSource::ExecuteSQL( pszSQLCommand,
                                        poSpatialFilter,
                                        pszDialect );
        if( poLayer )
            m_oSetSelectLayers.insert(poLayer);
        return poLayer;
    }

/* -------------------------------------------------------------------- */
/*      Run the SQL                                                     */
/* -------------------------------------------------------------------- */
    EnumRows* pEnumRows = new EnumRows;
    long hr;
    try
    {
        hr = m_pGeodatabase->ExecuteSQL(
                                StringToWString(pszSQLCommand), true, *pEnumRows);
    }
    catch(...)
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Exception occurred at executing '%s'. Application may "
                  "become unstable", pszSQLCommand );
        delete pEnumRows;
        return nullptr;
    }

    if (FAILED(hr))
    {
        GDBErr(hr, CPLSPrintf("Failed at executing '%s'", pszSQLCommand));
        delete pEnumRows;
        return nullptr;
    }

    if( STARTS_WITH_CI(pszSQLCommand, "SELECT ") )
    {
        OGRLayer* poLayer = new FGdbResultLayer(this, pszSQLCommand, pEnumRows);
        m_oSetSelectLayers.insert(poLayer);
        return poLayer;
    }
    else
    {
        delete pEnumRows;
        return nullptr;
    }
}

/************************************************************************/
/*                           ReleaseResultSet()                         */
/************************************************************************/

void FGdbDataSource::ReleaseResultSet( OGRLayer * poResultsSet )
{
    if( poResultsSet )
        m_oSetSelectLayers.erase(poResultsSet);
    delete poResultsSet;
}

/************************************************************************/
/*                      HasPerLayerCopyingForTransaction()              */
/************************************************************************/

int FGdbDataSource::HasPerLayerCopyingForTransaction()
{
    if( bPerLayerCopyingForTransaction >= 0 )
        return bPerLayerCopyingForTransaction;
#ifdef WIN32
    bPerLayerCopyingForTransaction = FALSE;
#else
    bPerLayerCopyingForTransaction =
        m_poOpenFileGDBDrv != nullptr &&
        CPLTestBool(CPLGetConfigOption("FGDB_PER_LAYER_COPYING_TRANSACTION", "TRUE"));
#endif
    return bPerLayerCopyingForTransaction;
}

/************************************************************************/
/*                        SetSymlinkFlagOnAllLayers()                    */
/************************************************************************/

void FGdbDataSource::SetSymlinkFlagOnAllLayers()
{
    size_t count = m_layers.size();
    for(size_t i = 0; i < count; ++i )
    {
        m_layers[i]->SetSymlinkFlag();
    }
}
