/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Implements OGRMySQLDataSource class.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 * Author:   Howard Butler, hobu@hobu.net
 *
 ******************************************************************************
 * Copyright (c) 2004, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
 *
 * 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 <string>
#include "ogr_mysql.h"

#include "cpl_conv.h"
#include "cpl_string.h"

CPL_CVSID("$Id: ogrmysqldatasource.cpp 8ca42e1b9c2e54b75d35e49885df9789a2643aa4 2020-05-17 21:43:40 +0200 Even Rouault $")
/************************************************************************/
/*                         OGRMySQLDataSource()                         */
/************************************************************************/

OGRMySQLDataSource::OGRMySQLDataSource() :
    papoLayers(nullptr),
    nLayers(0),
    pszName(nullptr),
    bDSUpdate(FALSE),
    hConn(nullptr),
    nKnownSRID(0),
    panSRID(nullptr),
    papoSRS(nullptr),
    poLongResultLayer(nullptr)
{}

/************************************************************************/
/*                        ~OGRMySQLDataSource()                         */
/************************************************************************/

OGRMySQLDataSource::~OGRMySQLDataSource()

{
    InterruptLongResult();

    CPLFree( pszName );

    for( int i = 0; i < nLayers; i++ )
        delete papoLayers[i];

    CPLFree( papoLayers );

    if( hConn != nullptr )
        mysql_close( hConn );

    for( int i = 0; i < nKnownSRID; i++ )
    {
        if( papoSRS[i] != nullptr )
            papoSRS[i]->Release();
    }
    CPLFree( panSRID );
    CPLFree( papoSRS );
}

/************************************************************************/
/*                          GetUnknownSRID()                            */
/************************************************************************/

int OGRMySQLDataSource::GetUnknownSRID() const
{
    return m_nMajor >= 8 && !m_bIsMariaDB ? 0 : -1;
}

/************************************************************************/
/*                            ReportError()                             */
/************************************************************************/

void OGRMySQLDataSource::ReportError( const char *pszDescription )

{
    if( pszDescription )
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MySQL error message:%s Description: %s",
                  mysql_error( hConn ),
                  pszDescription );
    else
        CPLError( CE_Failure, CPLE_AppDefined,
                  "%s", mysql_error( hConn ) );
}

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

int OGRMySQLDataSource::Open( const char * pszNewName, char** papszOpenOptionsIn,
                              int bUpdate )

{
    CPLAssert( nLayers == 0 );

/* -------------------------------------------------------------------- */
/*      Use options process to get .my.cnf file contents.               */
/* -------------------------------------------------------------------- */
    int nPort = 0;
    char **papszTableNames=nullptr;
    std::string oHost, oPassword, oUser, oDB;

    CPLString osNewName(pszNewName);
    const char* apszOpenOptions[] = { "dbname", "port", "user", "password",
                                      "host", "tables" };
    for(int i=0; i <(int)(sizeof(apszOpenOptions)/sizeof(char*));i++)
    {
        const char* pszVal = CSLFetchNameValue(papszOpenOptionsIn, apszOpenOptions[i]);
        if( pszVal )
        {
            if( osNewName.back() != ':' )
                osNewName += ",";
            if( i > 0 )
            {
                osNewName += apszOpenOptions[i];
                osNewName += "=";
            }
            if( EQUAL(apszOpenOptions[i], "tables") )
            {
                for( ; *pszVal; ++pszVal )
                {
                    if( *pszVal == ',' )
                        osNewName += ";";
                    else
                        osNewName += *pszVal;
                }
            }
            else
                osNewName += pszVal;
        }
    }

/* -------------------------------------------------------------------- */
/*      Parse out connection information.                               */
/* -------------------------------------------------------------------- */
    char **papszItems = CSLTokenizeString2( osNewName+6, ",",
                                            CSLT_HONOURSTRINGS );

    if( CSLCount(papszItems) < 1 )
    {
        CSLDestroy( papszItems );
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MYSQL: request missing databasename." );
        return FALSE;
    }

    oDB = papszItems[0];

    for( int i = 1; papszItems[i] != nullptr; i++ )
    {
        if( STARTS_WITH_CI(papszItems[i], "user=") )
            oUser = papszItems[i] + 5;
        else if( STARTS_WITH_CI(papszItems[i], "password=") )
            oPassword = papszItems[i] + 9;
        else if( STARTS_WITH_CI(papszItems[i], "host=") )
            oHost = papszItems[i] + 5;
        else if( STARTS_WITH_CI(papszItems[i], "port=") )
            nPort = atoi(papszItems[i] + 5);
        else if( STARTS_WITH_CI(papszItems[i], "tables=") )
        {
            CSLDestroy(papszTableNames);
            papszTableNames = CSLTokenizeStringComplex(
                papszItems[i] + 7, ";", FALSE, FALSE );
        }
        else
            CPLError( CE_Warning, CPLE_AppDefined,
                      "'%s' in MYSQL datasource definition not recognised and ignored.", papszItems[i] );
    }

    CSLDestroy( papszItems );

/* -------------------------------------------------------------------- */
/*      Try to establish connection.                                    */
/* -------------------------------------------------------------------- */
    hConn = mysql_init( nullptr );

    if( hConn == nullptr )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "mysql_init() failed." );
    }

/* -------------------------------------------------------------------- */
/*      Set desired options on the connection: charset and timeout.     */
/* -------------------------------------------------------------------- */
    if( hConn )
    {
        const char *pszTimeoutLength =
            CPLGetConfigOption( "MYSQL_TIMEOUT", "0" );

        unsigned int timeout = atoi(pszTimeoutLength);
        mysql_options(hConn, MYSQL_OPT_CONNECT_TIMEOUT, (char*)&timeout);

        mysql_options(hConn, MYSQL_SET_CHARSET_NAME, "utf8" );
    }

/* -------------------------------------------------------------------- */
/*      Perform connection.                                             */
/* -------------------------------------------------------------------- */
    if( hConn
        && mysql_real_connect( hConn,
                               oHost.length() ? oHost.c_str() : nullptr,
                               oUser.length() ? oUser.c_str() : nullptr,
                               oPassword.length() ? oPassword.c_str() : nullptr,
                               oDB.length() ? oDB.c_str() : nullptr,
                               nPort, nullptr, CLIENT_INTERACTIVE ) == nullptr )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "MySQL connect failed for: %s\n%s",
                  pszNewName + 6, mysql_error( hConn ) );
        mysql_close( hConn );
        hConn = nullptr;
    }

    if( hConn == nullptr )
    {
        CSLDestroy( papszTableNames );
        return FALSE;
    }
    else
    {
        // Enable automatic reconnection
#if defined(LIBMYSQL_VERSION_ID) && (LIBMYSQL_VERSION_ID >= 80000)
        bool reconnect = 1;
#else
        my_bool reconnect = 1;
#endif
        // Must be called after mysql_real_connect() on MySQL < 5.0.19
        // and at any point on more recent versions.
        mysql_options(hConn, MYSQL_OPT_RECONNECT, &reconnect);
    }

    pszName = CPLStrdup( pszNewName );

    bDSUpdate = bUpdate;

/* -------------------------------------------------------------------- */
/*      Check version.                                                  */
/* -------------------------------------------------------------------- */
    auto versionLyr = ExecuteSQL("SELECT VERSION()", nullptr, nullptr);
    if( versionLyr )
    {
        auto versionFeat = versionLyr->GetNextFeature();
        if( versionFeat )
        {
            const char* pszVersion = versionFeat->GetFieldAsString(0);
            m_nMajor = atoi(pszVersion);
            const char* pszDot = strchr(pszVersion, '.');
            if( pszDot )
                m_nMinor = atoi(pszDot+1);
            m_bIsMariaDB = strstr(pszVersion, "MariaDB") != nullptr;
        }
        delete versionFeat;
        ReleaseResultSet(versionLyr);
    }

/* -------------------------------------------------------------------- */
/*      Get a list of available tables.                                 */
/* -------------------------------------------------------------------- */
    if( papszTableNames == nullptr )
    {
        MYSQL_RES *hResultSet;
        MYSQL_ROW papszRow;

        if( mysql_query( hConn, "SHOW TABLES" ) )
        {
            ReportError( "SHOW TABLES Failed" );
            return FALSE;
        }

        hResultSet = mysql_store_result( hConn );
        if( hResultSet == nullptr )
        {
            ReportError( "mysql_store_result() failed on SHOW TABLES result.");
            return FALSE;
        }

        while( (papszRow = mysql_fetch_row( hResultSet )) != nullptr )
        {
            if( papszRow[0] == nullptr )
                continue;

            if( EQUAL(papszRow[0],"spatial_ref_sys")
                || EQUAL(papszRow[0],"geometry_columns") )
                continue;

            papszTableNames = CSLAddString(papszTableNames, papszRow[0] );
        }

        mysql_free_result( hResultSet );
    }

/* -------------------------------------------------------------------- */
/*      Get the schema of the available tables.                         */
/* -------------------------------------------------------------------- */
    for( int iRecord = 0;
         papszTableNames != nullptr && papszTableNames[iRecord] != nullptr;
         iRecord++ )
    {
        //  FIXME: This should be fixed to deal with tables
        //  for which we can't open because the name is bad/
        OpenTable( papszTableNames[iRecord], bUpdate );
    }

    CSLDestroy( papszTableNames );

    return nLayers > 0 || bUpdate;
}

/************************************************************************/
/*                             OpenTable()                              */
/************************************************************************/

int OGRMySQLDataSource::OpenTable( const char *pszNewName, int bUpdate )

{
/* -------------------------------------------------------------------- */
/*      Create the layer object.                                        */
/* -------------------------------------------------------------------- */
    OGRMySQLTableLayer  *poLayer;
    OGRErr eErr;

    poLayer = new OGRMySQLTableLayer( this, pszNewName, bUpdate );
    eErr = poLayer->Initialize(pszNewName);
    if (eErr == OGRERR_FAILURE)
        return FALSE;

/* -------------------------------------------------------------------- */
/*      Add layer to data source layer list.                            */
/* -------------------------------------------------------------------- */
    papoLayers = (OGRMySQLLayer **)
        CPLRealloc( papoLayers,  sizeof(OGRMySQLLayer *) * (nLayers+1) );
    papoLayers[nLayers++] = poLayer;

    return TRUE;
}

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

int OGRMySQLDataSource::TestCapability( const char * pszCap )

{
    if( EQUAL(pszCap, ODsCCreateLayer) )
        return TRUE;
    if( EQUAL(pszCap, ODsCDeleteLayer))
        return TRUE;
    if( EQUAL(pszCap,ODsCRandomLayerWrite) )
        return TRUE;
    else
        return FALSE;
}

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

OGRLayer *OGRMySQLDataSource::GetLayer( int iLayer )

{
    if( iLayer < 0 || iLayer >= nLayers )
        return nullptr;
    else
        return papoLayers[iLayer];
}

/* =====================================================================*/
/* Handle spatial reference id and index for older MySQL and MariaDB    */
/* =====================================================================*/

/************************************************************************/
/*                      InitializeMetadataTables()                      */
/*                                                                      */
/*      Create the metadata tables (SPATIAL_REF_SYS and                 */
/*      GEOMETRY_COLUMNS). This method "does no harm" if the tables     */
/*      exist and can be called at will.                                */
/************************************************************************/

OGRErr OGRMySQLDataSource::InitializeMetadataTables()

{
    const char*      pszCommand;
    MYSQL_RES       *hResult;
    OGRErr          eErr = OGRERR_NONE;

    if( GetMajorVersion() < 8 || IsMariaDB() )
    {
        pszCommand = "DESCRIBE geometry_columns";
        if( mysql_query(GetConn(), pszCommand ) )
        {
            pszCommand =
                    "CREATE TABLE geometry_columns "
                    "( F_TABLE_CATALOG VARCHAR(256), "
                    "F_TABLE_SCHEMA VARCHAR(256), "
                    "F_TABLE_NAME VARCHAR(256) NOT NULL,"
                    "F_GEOMETRY_COLUMN VARCHAR(256) NOT NULL, "
                    "COORD_DIMENSION INT, "
                    "SRID INT,"
                    "TYPE VARCHAR(256) NOT NULL)";
            if( mysql_query(GetConn(), pszCommand ) )
            {
                ReportError( pszCommand );
                eErr = OGRERR_FAILURE;
            }
            else
                CPLDebug("MYSQL","Creating geometry_columns metadata table");
        }

        // make sure to attempt to free results of successful queries
        hResult = mysql_store_result( GetConn() );
        if( hResult != nullptr )
        {
            mysql_free_result( hResult );
            hResult = nullptr;
        }

        pszCommand = "DESCRIBE spatial_ref_sys";
        if( mysql_query(GetConn(), pszCommand ) )
        {
            pszCommand =
                    "CREATE TABLE spatial_ref_sys "
                    "(SRID INT NOT NULL, "
                    "AUTH_NAME VARCHAR(256), "
                    "AUTH_SRID INT, "
                    "SRTEXT VARCHAR(2048))";
            if( mysql_query(GetConn(), pszCommand ) )
            {
                ReportError( pszCommand );
                eErr = OGRERR_FAILURE;
            }
            else
                CPLDebug("MYSQL","Creating spatial_ref_sys metadata table");
        }

        // make sure to attempt to free results of successful queries
        hResult = mysql_store_result( GetConn() );
        if( hResult != nullptr )
        {
            mysql_free_result( hResult );
            hResult = nullptr;
        }
    }

    return eErr;
}

OGRErr OGRMySQLDataSource::UpdateMetadataTables(const char *pszLayerName,
                                                OGRwkbGeometryType eType,
                                                const char *pszGeomColumnName,
                                                const int nSRSId)

{
    MYSQL_RES *hResult = nullptr;
    CPLString osCommand;
    const char *pszGeometryType;

    if (GetMajorVersion() < 8 || IsMariaDB())
    {
/* -------------------------------------------------------------------- */
/*      Sometimes there is an old crufty entry in the geometry_columns  */
/*      table if things were not properly cleaned up before.  We make   */
/*      an effort to clean out such cruft.                              */
/*                                                                      */
/* -------------------------------------------------------------------- */
        osCommand.Printf(
                "DELETE FROM geometry_columns WHERE f_table_name = '%s'",
                pszLayerName);

        if (mysql_query(GetConn(), osCommand))
        {
                ReportError(osCommand);
                return OGRERR_FAILURE;
        }

        // make sure to attempt to free results of successful queries
        hResult = mysql_store_result(GetConn());
        if (hResult != nullptr)
            mysql_free_result(hResult);
        hResult = nullptr;

/* -------------------------------------------------------------------- */
/*      Attempt to add this table to the geometry_columns table, if     */
/*      it is a spatial layer.                                          */
/* -------------------------------------------------------------------- */
        if (eType != wkbNone) {
            const int nCoordDimension = eType == wkbFlatten(eType) ? 2 : 3;

            pszGeometryType = OGRToOGCGeomType(eType);

            if (nSRSId == GetUnknownSRID())
                osCommand.Printf(
                        "INSERT INTO geometry_columns "
                        " (F_TABLE_NAME, "
                        "  F_GEOMETRY_COLUMN, "
                        "  COORD_DIMENSION, "
                        "  TYPE) values "
                        "  ('%s', '%s', %d, '%s')",
                        pszLayerName,
                        pszGeomColumnName,
                        nCoordDimension,
                        pszGeometryType);
            else
                osCommand.Printf(
                        "INSERT INTO geometry_columns "
                        " (F_TABLE_NAME, "
                        "  F_GEOMETRY_COLUMN, "
                        "  COORD_DIMENSION, "
                        "  SRID, "
                        "  TYPE) values "
                        "  ('%s', '%s', %d, %d, '%s')",
                        pszLayerName,
                        pszGeomColumnName,
                        nCoordDimension,
                        nSRSId,
                        pszGeometryType);

            if (mysql_query(GetConn(), osCommand)) {
                ReportError(osCommand);
                return OGRERR_FAILURE;
            }

            // make sure to attempt to free results of successful queries
            hResult = mysql_store_result(GetConn());
            if (hResult != nullptr)
                mysql_free_result(hResult);
            hResult = nullptr;
        }
    }
    return OGRERR_NONE;
}

/* =====================================================================*/

/************************************************************************/
/*                              FetchSRS()                              */
/*                                                                      */
/*      Return a SRS corresponding to a particular id.  Note that       */
/*      reference counting should be honoured on the returned           */
/*      OGRSpatialReference, as handles may be cached.                  */
/************************************************************************/

OGRSpatialReference *OGRMySQLDataSource::FetchSRS( int nId )
{
    if( nId < 0 )
        return nullptr;

/* -------------------------------------------------------------------- */
/*      First, we look through our SRID cache, is it there?             */
/* -------------------------------------------------------------------- */
    for( int i = 0; i < nKnownSRID; i++ )
    {
        if( panSRID[i] == nId )
            return papoSRS[i];
    }

    OGRSpatialReference *poSRS = nullptr;

    // make sure to attempt to free any old results
    MYSQL_RES *hResult = mysql_store_result( GetConn() );
    if( hResult != nullptr )
        mysql_free_result( hResult );
    hResult = nullptr;

    char szCommand[128] = {};
    if( GetMajorVersion() < 8 || IsMariaDB() )
    {
        snprintf( szCommand, sizeof(szCommand),
                "SELECT srtext FROM spatial_ref_sys WHERE srid = %d",
                nId );
    }
    else
    {
        snprintf( szCommand, sizeof(szCommand),
                "SELECT DEFINITION FROM INFORMATION_SCHEMA.ST_SPATIAL_REFERENCE_SYSTEMS WHERE SRS_ID = %d",
                nId );
    }

    if( !mysql_query( GetConn(), szCommand ) )
        hResult = mysql_store_result( GetConn() );

    char  *pszWKT = nullptr;
    char **papszRow = nullptr;

    if( hResult != nullptr )
        papszRow = mysql_fetch_row( hResult );

    if( papszRow != nullptr && papszRow[0] != nullptr )
    {
        pszWKT = CPLStrdup(papszRow[0]);
    }

    if( hResult != nullptr )
        mysql_free_result( hResult );
    hResult = nullptr;

    poSRS = new OGRSpatialReference();
    poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    if( pszWKT == nullptr || poSRS->importFromWkt( pszWKT ) != OGRERR_NONE )
    {
        delete poSRS;
        poSRS = nullptr;
    }

    CPLFree(pszWKT);

    if( poSRS )
    {
        // The WKT found in MySQL 8 ST_SPATIAL_REFERENCE_SYSTEMS is not
        // compatible of what GDAL understands.
        const char* pszAuthorityName = poSRS->GetAuthorityName(nullptr);
        const char* pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
        if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG") &&
            pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0 )
        {
            /* Import 'clean' SRS */
            poSRS->importFromEPSG( atoi(pszAuthorityCode) );
        }
    }

/* -------------------------------------------------------------------- */
/*      Add to the cache.                                               */
/* -------------------------------------------------------------------- */
    panSRID = (int *) CPLRealloc(panSRID,sizeof(int) * (nKnownSRID+1) );
    papoSRS = (OGRSpatialReference **)
        CPLRealloc(papoSRS, sizeof(void*) * (nKnownSRID + 1) );
    panSRID[nKnownSRID] = nId;
    papoSRS[nKnownSRID] = poSRS;
    nKnownSRID ++;

    return poSRS;
}

/************************************************************************/
/*                             FetchSRSId()                             */
/*                                                                      */
/*      Fetch the id corresponding to an SRS, and if not found, add     */
/*      it to the table.                                                */
/************************************************************************/

int OGRMySQLDataSource::FetchSRSId( OGRSpatialReference * poSRS )

{
    if( poSRS == nullptr )
        return GetUnknownSRID();

    OGRSpatialReference oSRS(*poSRS);
    // cppcheck-suppress uselessAssignmentPtrArg
    poSRS = nullptr;

    const char* pszAuthorityName = oSRS.GetAuthorityName(nullptr);

    if( pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0 )
    {
/* -------------------------------------------------------------------- */
/*      Try to identify an EPSG code                                    */
/* -------------------------------------------------------------------- */
        oSRS.AutoIdentifyEPSG();

        pszAuthorityName = oSRS.GetAuthorityName(nullptr);
        if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG"))
        {
            const char* pszAuthorityCode = oSRS.GetAuthorityCode(nullptr);
            if ( pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0 )
            {
                /* Import 'clean' SRS */
                oSRS.importFromEPSG( atoi(pszAuthorityCode) );

                pszAuthorityName = oSRS.GetAuthorityName(nullptr);
            }
        }
    }
/* -------------------------------------------------------------------- */
/*      Check whether the authority name/code is already mapped to a    */
/*      SRS ID.                                                         */
/* -------------------------------------------------------------------- */
    CPLString osCommand;
    if( pszAuthorityName != nullptr )
    {
        /* Check that the authority code is integral */
        const int nAuthorityCode = atoi( oSRS.GetAuthorityCode(nullptr) );
        if( nAuthorityCode > 0 )
        {
            if( GetMajorVersion() < 8 || IsMariaDB() )
            {
                osCommand.Printf(
                        "SELECT srid FROM spatial_ref_sys WHERE "
                        "auth_name = '%s' AND auth_srid = %d",
                        pszAuthorityName,
                        nAuthorityCode );
            }
            else
            {
                osCommand.Printf(
                        "SELECT SRS_ID FROM INFORMATION_SCHEMA.ST_SPATIAL_REFERENCE_SYSTEMS "
                        "WHERE ORGANIZATION = '%s' AND ORGANIZATION_COORDSYS_ID = %d",
                        pszAuthorityName,
                        nAuthorityCode );
            }

            MYSQL_RES *hResult = nullptr;
            if( !mysql_query( GetConn(), osCommand ) )
                hResult = mysql_store_result( GetConn() );

            if ( hResult != nullptr && !mysql_num_rows(hResult))
            {
                CPLDebug("MYSQL", "No rows exist currently exist in spatial_ref_sys");
                mysql_free_result( hResult );
                hResult = nullptr;
            }
            char **papszRow = nullptr;
            if( hResult != nullptr )
                papszRow = mysql_fetch_row( hResult );

            if( papszRow != nullptr && papszRow[0] != nullptr )
            {
                const int nSRSId = atoi(papszRow[0]);
                if( hResult != nullptr )
                    mysql_free_result( hResult );
                hResult = nullptr;
                return nSRSId;
            }

            // make sure to attempt to free results of successful queries
            hResult = mysql_store_result( GetConn() );
            if( hResult != nullptr )
                mysql_free_result( hResult );
        }
    }

/* -------------------------------------------------------------------- */
/*      Translate SRS to WKT.                                           */
/* -------------------------------------------------------------------- */
    char *pszWKT = nullptr;
    if( oSRS.exportToWkt( &pszWKT ) != OGRERR_NONE )
    {
        CPLFree(pszWKT);
        return GetUnknownSRID();
    }

/* -------------------------------------------------------------------- */
/*      Try to find in the existing record.                             */
/* -------------------------------------------------------------------- */
    if( GetMajorVersion() < 8 || IsMariaDB() )
    {
        osCommand.Printf(
                "SELECT srid FROM spatial_ref_sys WHERE srtext = '%s'",
                pszWKT );
    }
    else
    {
        osCommand.Printf(
                "SELECT SRS_ID FROM INFORMATION_SCHEMA.ST_SPATIAL_REFERENCE_SYSTEMS WHERE DEFINITION = '%s'",
                pszWKT );
    }

    MYSQL_RES *hResult = nullptr;
    if( !mysql_query( GetConn(), osCommand ) )
        hResult = mysql_store_result( GetConn() );

    if ( hResult != nullptr && !mysql_num_rows(hResult))
    {
        CPLDebug("MYSQL", "No rows exist currently exist in spatial_ref_sys");
        mysql_free_result( hResult );
        hResult = nullptr;
    }
    char **papszRow = nullptr;
    if( hResult != nullptr )
        papszRow = mysql_fetch_row( hResult );

    if( papszRow != nullptr && papszRow[0] != nullptr )
    {
        const int nSRSId = atoi(papszRow[0]);
        if( hResult != nullptr )
            mysql_free_result( hResult );
        hResult = nullptr;
        CPLFree(pszWKT);
        return nSRSId;
    }

    // make sure to attempt to free results of successful queries
    hResult = mysql_store_result( GetConn() );
    if( hResult != nullptr )
        mysql_free_result( hResult );
    hResult = nullptr;

    // TODO: try to insert in INFORMATION_SCHEMA.ST_SPATIAL_REFERENCE_SYSTEMS
    if( GetMajorVersion() >= 8 && !IsMariaDB() )
    {
        CPLFree(pszWKT);
        return GetUnknownSRID();
    }

/* -------------------------------------------------------------------- */
/*      Get the current maximum srid in the srs table.                  */
/* -------------------------------------------------------------------- */
    osCommand = "SELECT MAX(srid) FROM spatial_ref_sys";
    if( !mysql_query( GetConn(), osCommand ) )
    {
        hResult = mysql_store_result( GetConn() );
        papszRow = mysql_fetch_row( hResult );
    }

    const int nSRSId = papszRow != nullptr && papszRow[0] != nullptr
        ? atoi(papszRow[0]) + 1
        : 1;

    if( hResult != nullptr )
        mysql_free_result( hResult );
    hResult = nullptr;

/* -------------------------------------------------------------------- */
/*      Try adding the SRS to the SRS table.                            */
/* -------------------------------------------------------------------- */
    osCommand.Printf(
             "INSERT INTO spatial_ref_sys (srid,srtext) VALUES (%d,'%s')",
             nSRSId, pszWKT );

    if( !mysql_query( GetConn(), osCommand ) )
        /*hResult = */ mysql_store_result( GetConn() ); /* FIXME ? */

    // make sure to attempt to free results of successful queries
    hResult = mysql_store_result( GetConn() );
    if( hResult != nullptr )
        mysql_free_result( hResult );
    hResult = nullptr;

    CPLFree(pszWKT);

    return nSRSId;
}

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

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

{
    if( poSpatialFilter != nullptr )
    {
        CPLDebug( "OGR_MYSQL",
          "Spatial filter ignored for now in OGRMySQLDataSource::ExecuteSQL()" );
    }

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

/* -------------------------------------------------------------------- */
/*      Special case DELLAYER: command.                                 */
/* -------------------------------------------------------------------- */
#ifdef notdef
    if( STARTS_WITH_CI(pszSQLCommand, "DELLAYER:") )
    {
        const char *pszLayerName = pszSQLCommand + 9;

        while( *pszLayerName == ' ' )
            pszLayerName++;

        DeleteLayer( pszLayerName );
        return NULL;
    }
#endif

/* -------------------------------------------------------------------- */
/*      Make sure there isn't an active transaction already.            */
/* -------------------------------------------------------------------- */
    InterruptLongResult();

/* -------------------------------------------------------------------- */
/*      Execute the statement.                                          */
/* -------------------------------------------------------------------- */
    MYSQL_RES *hResultSet;

    if( mysql_query( hConn, pszSQLCommand ) )
    {
        ReportError( pszSQLCommand );
        return nullptr;
    }

    hResultSet = mysql_use_result( hConn );
    if( hResultSet == nullptr )
    {
        if( mysql_field_count( hConn ) == 0 )
        {
            CPLDebug( "MYSQL", "Command '%s' succeeded, %d rows affected.",
                      pszSQLCommand,
                      (int) mysql_affected_rows(hConn) );
            return nullptr;
        }
        else
        {
            ReportError( pszSQLCommand );
            return nullptr;
        }
    }

/* -------------------------------------------------------------------- */
/*      Do we have a tuple result? If so, instantiate a results         */
/*      layer for it.                                                   */
/* -------------------------------------------------------------------- */

    OGRMySQLResultLayer *poLayer =
        new OGRMySQLResultLayer( this, pszSQLCommand, hResultSet );

    return poLayer;
}

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

void OGRMySQLDataSource::ReleaseResultSet( OGRLayer * poLayer )

{
    delete poLayer;
}

/************************************************************************/
/*                            LaunderName()                             */
/************************************************************************/

char *OGRMySQLDataSource::LaunderName( const char *pszSrcName )

{
    char *pszSafeName = CPLStrdup( pszSrcName );

    for( int i = 0; pszSafeName[i] != '\0'; i++ )
    {
        pszSafeName[i] = (char) tolower( pszSafeName[i] );
        if( pszSafeName[i] == '-' || pszSafeName[i] == '#' )
            pszSafeName[i] = '_';
    }

    return pszSafeName;
}

/************************************************************************/
/*                         RequestLongResult()                          */
/*                                                                      */
/*      Layers need to use mysql_use_result() instead of                */
/*      mysql_store_result() so that we won't have to load entire       */
/*      result sets into RAM.  But only one "streamed" resultset can    */
/*      be active on a database connection at a time.  So we need to    */
/*      maintain a way of closing off an active streaming resultset     */
/*      before any other sort of query with a resultset is              */
/*      executable.  This method (and InterruptLongResult())            */
/*      implement that exclusion.                                       */
/************************************************************************/

void OGRMySQLDataSource::RequestLongResult( OGRMySQLLayer * poNewLayer )

{
    InterruptLongResult();
    poLongResultLayer = poNewLayer;
}

/************************************************************************/
/*                        InterruptLongResult()                         */
/************************************************************************/

void OGRMySQLDataSource::InterruptLongResult()

{
    if( poLongResultLayer != nullptr )
    {
        poLongResultLayer->ResetReading();
        poLongResultLayer = nullptr;
    }
}

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

OGRErr OGRMySQLDataSource::DeleteLayer( int iLayer)

{
    if( iLayer < 0 || iLayer >= nLayers )
        return OGRERR_FAILURE;

/* -------------------------------------------------------------------- */
/*      Blow away our OGR structures related to the layer.  This is     */
/*      pretty dangerous if anything has a reference to this layer!     */
/* -------------------------------------------------------------------- */
    CPLString osLayerName = papoLayers[iLayer]->GetLayerDefn()->GetName();

    CPLDebug( "MYSQL", "DeleteLayer(%s)", osLayerName.c_str() );

    delete papoLayers[iLayer];
    memmove( papoLayers + iLayer, papoLayers + iLayer + 1,
             sizeof(void *) * (nLayers - iLayer - 1) );
    nLayers--;

/* -------------------------------------------------------------------- */
/*      Remove from the database.                                       */
/* -------------------------------------------------------------------- */
    CPLString osCommand;

    osCommand.Printf(
             "DROP TABLE `%s` ",
             osLayerName.c_str() );

    if( !mysql_query(GetConn(), osCommand ) )
    {
        CPLDebug("MYSQL","Dropped table %s.", osLayerName.c_str());
        return OGRERR_NONE;
    }
    else
    {
        ReportError( osCommand );
        return OGRERR_FAILURE;
    }
}

/************************************************************************/
/*                           ICreateLayer()                             */
/************************************************************************/

OGRLayer *
OGRMySQLDataSource::ICreateLayer( const char * pszLayerNameIn,
                              OGRSpatialReference *poSRS,
                              OGRwkbGeometryType eType,
                              char ** papszOptions )

{
    MYSQL_RES  *hResult=nullptr;
    CPLString   osCommand;
    const char *pszGeomColumnName;
    const char *pszExpectedFIDName;
    char       *pszLayerName;
    // int        nDimension = 3; // MySQL only supports 2d currently

/* -------------------------------------------------------------------- */
/*      Make sure there isn't an active transaction already.            */
/* -------------------------------------------------------------------- */
    InterruptLongResult();

    if( CPLFetchBool(papszOptions, "LAUNDER", true) )
        pszLayerName = LaunderName( pszLayerNameIn );
    else
        pszLayerName = CPLStrdup( pszLayerNameIn );

    // if( wkbFlatten(eType) == eType )
    //    nDimension = 2;

    CPLDebug("MYSQL","Creating layer %s.", pszLayerName);

/* -------------------------------------------------------------------- */
/*      Do we already have this layer?  If so, should we blow it        */
/*      away?                                                           */
/* -------------------------------------------------------------------- */

    for( int iLayer = 0; iLayer < nLayers; iLayer++ )
    {
        if( EQUAL(pszLayerName,papoLayers[iLayer]->GetLayerDefn()->GetName()) )
        {

            if( CSLFetchNameValue( papszOptions, "OVERWRITE" ) != nullptr
                && !EQUAL(CSLFetchNameValue(papszOptions,"OVERWRITE"),"NO") )
            {
                DeleteLayer( iLayer );
            }
            else
            {
                CPLError( CE_Failure, CPLE_AppDefined,
                          "Layer %s already exists, CreateLayer failed.\n"
                          "Use the layer creation option OVERWRITE=YES to "
                          "replace it.",
                          pszLayerName );
                CPLFree( pszLayerName );
                return nullptr;
            }
        }
    }

    pszGeomColumnName = CSLFetchNameValue( papszOptions, "GEOMETRY_NAME" );
    if (!pszGeomColumnName)
        pszGeomColumnName="SHAPE";

    pszExpectedFIDName = CSLFetchNameValue( papszOptions, "FID" );
    if (!pszExpectedFIDName)
        pszExpectedFIDName = CSLFetchNameValue( papszOptions, "MYSQL_FID" );
    if (!pszExpectedFIDName)
        pszExpectedFIDName="OGR_FID";

    const bool bFID64 = CPLFetchBool(papszOptions, "FID64", false);
    const char* pszFIDType = bFID64 ? "BIGINT": "INT";

    CPLDebug("MYSQL","Geometry Column Name %s.", pszGeomColumnName);
    CPLDebug("MYSQL","FID Column Name %s.", pszExpectedFIDName);

    const char *pszSI = CSLFetchNameValue( papszOptions, "SPATIAL_INDEX" );
    const bool bHasSI = ( eType != wkbNone && (pszSI == nullptr || CPLTestBool(pszSI)) );

    // Calling this does no harm
    InitializeMetadataTables();

/* -------------------------------------------------------------------- */
/*      Try to get the SRS Id of this spatial reference system,         */
/*      adding to the srs table if needed.                             */
/* -------------------------------------------------------------------- */

    int nSRSId = GetUnknownSRID();
    if (poSRS != nullptr)
        nSRSId = FetchSRSId(poSRS);

    if( wkbFlatten(eType) == wkbNone )
    {
        osCommand.Printf(
                 "CREATE TABLE `%s` ( "
                 "   %s %s UNIQUE NOT NULL AUTO_INCREMENT )",
                 pszLayerName, pszExpectedFIDName, pszFIDType );
    }
    else
    {
        // when using mysql8 and SRS is specified, use SRID option for geometry.
        if( GetMajorVersion() < 8 || IsMariaDB() || nSRSId == GetUnknownSRID() )
            osCommand.Printf(
                    "CREATE TABLE `%s` ( "
                    "   %s %s UNIQUE NOT NULL AUTO_INCREMENT, "
                    "   %s GEOMETRY %s)",
                    pszLayerName, pszExpectedFIDName, pszFIDType, pszGeomColumnName,
                    bHasSI ? "NOT NULL" : "");
        else
            osCommand.Printf(
                    "CREATE TABLE `%s` ( "
                    "   %s %s UNIQUE NOT NULL AUTO_INCREMENT, "
                    "   %s GEOMETRY %s /*!80003 SRID %d */)",
                    pszLayerName, pszExpectedFIDName, pszFIDType, pszGeomColumnName,
                    bHasSI ? "NOT NULL" : "", nSRSId);
    }

    if( CSLFetchNameValue( papszOptions, "ENGINE" ) != nullptr )
    {
        osCommand += " ENGINE = ";
        osCommand += CSLFetchNameValue( papszOptions, "ENGINE" );
    }

    if( !mysql_query(GetConn(), osCommand ) )
    {
        if( mysql_field_count( GetConn() ) == 0 )
            CPLDebug("MYSQL","Created table %s.", pszLayerName);
        else
        {
            ReportError( osCommand );
            return nullptr;
        }
    }
    else
    {
        ReportError( osCommand );
        return nullptr;
    }

    // make sure to attempt to free results of successful queries
    hResult = mysql_store_result( GetConn() );
    if( hResult != nullptr )
        mysql_free_result( hResult );
    hResult = nullptr;

    if( UpdateMetadataTables(pszLayerName, eType, pszGeomColumnName, nSRSId) != OGRERR_NONE )
        return nullptr;

/* -------------------------------------------------------------------- */
/*      Create the spatial index.                                       */
/*                                                                      */
/*      We're doing this before we add geometry and record to the table */
/*      so this may not be exactly the best way to do it.               */
/* -------------------------------------------------------------------- */
    if( bHasSI )
    {
        osCommand.Printf(
                 "ALTER TABLE `%s` ADD SPATIAL INDEX(`%s`) ",
                 pszLayerName,
                 pszGeomColumnName);

        if( mysql_query(GetConn(), osCommand ) )
        {
            ReportError( osCommand );
            return nullptr;
        }

        // make sure to attempt to free results of successful queries
        hResult = mysql_store_result( GetConn() );
        if( hResult != nullptr )
            mysql_free_result( hResult );
        hResult = nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Create the layer object.                                        */
/* -------------------------------------------------------------------- */
    OGRMySQLTableLayer     *poLayer;
    OGRErr                  eErr;

    poLayer = new OGRMySQLTableLayer( this, pszLayerName, TRUE, nSRSId );
    eErr = poLayer->Initialize(pszLayerName);
    if (eErr == OGRERR_FAILURE)
        return nullptr;
    if( eType != wkbNone )
        poLayer->GetLayerDefn()->GetGeomFieldDefn(0)->SetNullable(FALSE);

    poLayer->SetLaunderFlag( CPLFetchBool(papszOptions, "LAUNDER", true) );
    poLayer->SetPrecisionFlag( CPLFetchBool(papszOptions, "PRECISION", true));

/* -------------------------------------------------------------------- */
/*      Add layer to data source layer list.                            */
/* -------------------------------------------------------------------- */
    papoLayers = (OGRMySQLLayer **)
        CPLRealloc( papoLayers,  sizeof(OGRMySQLLayer *) * (nLayers+1) );

    papoLayers[nLayers++] = poLayer;

    CPLFree( pszLayerName );

    return poLayer;
}
