/******************************************************************************
 *
 * Project:  GFT Translator
 * Purpose:  Implements OGRGFTTableLayer class.
 * Author:   Even Rouault, <even dot rouault at mines dash paris dot org>
 *
 ******************************************************************************
 * 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_gft.h"

#include <algorithm>

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

/************************************************************************/
/*                         OGRGFTTableLayer()                           */
/************************************************************************/

OGRGFTTableLayer::OGRGFTTableLayer( OGRGFTDataSource* poDSIn,
                                    const char* pszTableName,
                                    const char* pszTableId,
                                    const char* pszGeomColumnName ) :
    OGRGFTLayer(poDSIn),
    osTableName(pszTableName),
    osTableId(pszTableId),
    osGeomColumnName(pszGeomColumnName ? pszGeomColumnName : ""),
    bHasTriedCreateTable(FALSE),
    bInTransaction(FALSE),
    nFeaturesInTransaction(0),
    eGTypeForCreation(wkbUnknown)
{
    bFirstTokenIsFID = TRUE;

    SetDescription( osTableName );

    if( osTableId.empty() )
    {
        poFeatureDefn = new OGRFeatureDefn( osTableName );
        poFeatureDefn->Reference();
        poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
        poFeatureDefn->GetGeomFieldDefn(0)->
            SetName(GetDefaultGeometryColumnName());
    }
}

/************************************************************************/
/*                        ~OGRGFTTableLayer()                           */
/************************************************************************/

OGRGFTTableLayer::~OGRGFTTableLayer()

{
    CreateTableIfNecessary();
}

/************************************************************************/
/*                            ResetReading()                            */
/************************************************************************/

void OGRGFTTableLayer::ResetReading()

{
    OGRGFTLayer::ResetReading();
    aosRows.resize(0);
}

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

int OGRGFTTableLayer::TestCapability( const char * pszCap )

{
    if( EQUAL(pszCap,OLCRandomRead) )
        return TRUE;

    else if( EQUAL(pszCap,OLCSequentialWrite)
             || EQUAL(pszCap,OLCRandomWrite)
             || EQUAL(pszCap,OLCDeleteFeature) )
        return poDS->IsReadWrite();

    else if( EQUAL(pszCap,OLCCreateField) )
        return poDS->IsReadWrite();

    else if( EQUAL(pszCap, OLCTransactions) )
        return poDS->IsReadWrite();

    return OGRGFTLayer::TestCapability(pszCap);
}

/************************************************************************/
/*                           FetchDescribe()                            */
/************************************************************************/

int OGRGFTTableLayer::FetchDescribe()
{
    poFeatureDefn = new OGRFeatureDefn( osTableName );
    poFeatureDefn->Reference();
    poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);

    const CPLString& osAuth = poDS->GetAccessToken();
    std::vector<CPLString> aosHeaderAndFirstDataLine;
    if (!osAuth.empty() )
    {
        CPLString osSQL("DESCRIBE ");
        osSQL += osTableId;
        CPLHTTPResult * psResult = poDS->RunSQL(osSQL);

        if (psResult == nullptr)
            return FALSE;

        char* pszLine = (char*) psResult->pabyData;
        if (pszLine == nullptr ||
            psResult->pszErrBuf != nullptr ||
            !STARTS_WITH(pszLine, "column id,name,type"))        {
            CPLHTTPDestroyResult(psResult);
            return FALSE;
        }

        pszLine = OGRGFTGotoNextLine(pszLine);

        std::vector<CPLString> aosLines;
        ParseCSVResponse(pszLine, aosLines);
        for(int i=0;i<(int)aosLines.size();i++)
        {
            char** papszTokens = OGRGFTCSVSplitLine(aosLines[i], ',');
            if (CSLCount(papszTokens) == 3)
            {
                aosColumnInternalName.push_back(papszTokens[0]);

                //CPLDebug("GFT", "%s %s %s", papszTokens[0], papszTokens[1], papszTokens[2]);
                OGRFieldType eType = OFTString;
                if (EQUAL(papszTokens[2], "number"))
                    eType = OFTReal;
                else if (EQUAL(papszTokens[2], "datetime"))
                    eType = OFTDateTime;

                if (EQUAL(papszTokens[2], "location") && osGeomColumnName.empty())
                {
                    if (iGeometryField < 0)
                        iGeometryField = poFeatureDefn->GetFieldCount();
                    else
                        CPLDebug("GFT", "Multiple geometry fields detected. "
                                         "Only first encountered one is handled");
                }

                CPLString osLaunderedColName(LaunderColName(papszTokens[1]));
                OGRFieldDefn oFieldDefn(osLaunderedColName, eType);
                poFeatureDefn->AddFieldDefn(&oFieldDefn);
            }
            CSLDestroy(papszTokens);
        }

        CPLHTTPDestroyResult(psResult);
    }
    else
    {
        /* http://code.google.com/intl/fr/apis/fusiontables/docs/developers_guide.html#Exploring states */
        /* that DESCRIBE should work on public tables without authentication, but it is not true... */
        CPLString osSQL("SELECT * FROM ");
        osSQL += osTableId;
        osSQL += " OFFSET 0 LIMIT 1";

        CPLHTTPResult * psResult = poDS->RunSQL(osSQL);

        if (psResult == nullptr)
            return FALSE;

        char* pszLine = (char*) psResult->pabyData;
        if (pszLine == nullptr || psResult->pszErrBuf != nullptr)
        {
            CPLHTTPDestroyResult(psResult);
            return FALSE;
        }

        ParseCSVResponse(pszLine, aosHeaderAndFirstDataLine);
        if (!aosHeaderAndFirstDataLine.empty())
        {
            char** papszTokens = OGRGFTCSVSplitLine(aosHeaderAndFirstDataLine[0], ',');
            for(int i=0;papszTokens && papszTokens[i];i++)
            {
                CPLString osLaunderedColName(LaunderColName(papszTokens[i]));
                OGRFieldDefn oFieldDefn(osLaunderedColName, OFTString);
                poFeatureDefn->AddFieldDefn(&oFieldDefn);
            }
            CSLDestroy(papszTokens);
        }

        CPLHTTPDestroyResult(psResult);
    }

    if (!osGeomColumnName.empty())
    {
        iGeometryField = poFeatureDefn->GetFieldIndex(osGeomColumnName);
        if (iGeometryField < 0)
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "Cannot find column called %s", osGeomColumnName.c_str());
        }
    }

    for(int i=0;i<poFeatureDefn->GetFieldCount();i++)
    {
        const char* pszName = poFeatureDefn->GetFieldDefn(i)->GetNameRef();
        if (EQUAL(pszName, "latitude") || EQUAL(pszName, "lat") ||
            EQUAL(pszName, "latdec"))
            iLatitudeField = i;
        else if (EQUAL(pszName, "longitude") || EQUAL(pszName, "lon") ||
                 EQUAL(pszName, "londec") || EQUAL(pszName, "long"))
            iLongitudeField = i;
    }

    if (iLatitudeField >= 0 && iLongitudeField >= 0)
    {
        if (iGeometryField < 0)
            iGeometryField = iLatitudeField;
        poFeatureDefn->GetFieldDefn(iLatitudeField)->SetType(OFTReal);
        poFeatureDefn->GetFieldDefn(iLongitudeField)->SetType(OFTReal);
        poFeatureDefn->SetGeomType( wkbPoint );
    }
    else if (iGeometryField < 0 && osGeomColumnName.empty())
    {
        iLatitudeField = -1;
        iLongitudeField = -1;

        /* In the unauthenticated case, we try to parse the first record to */
        /* auto-detect the geometry field. */
        OGRwkbGeometryType eType = wkbUnknown;
        if (aosHeaderAndFirstDataLine.size() == 2)
        {
            char** papszTokens = OGRGFTCSVSplitLine(aosHeaderAndFirstDataLine[1], ',');
            if (CSLCount(papszTokens) == poFeatureDefn->GetFieldCount())
            {
                for(int i=0;i<poFeatureDefn->GetFieldCount();i++)
                {
                    const char* pszVal = papszTokens[i];
                    if (pszVal != nullptr &&
                        (STARTS_WITH(pszVal, "<Point>") ||
                         STARTS_WITH(pszVal, "<LineString>") ||
                         STARTS_WITH(pszVal, "<Polygon>") ||
                         STARTS_WITH(pszVal, "<MultiGeometry>")))
                    {
                        if (iGeometryField < 0)
                        {
                            iGeometryField = i;
                        }
                        else
                        {
                            CPLDebug("GFT", "Multiple geometry fields detected. "
                                     "Only first encountered one is handled");
                        }
                    }
                    else if (pszVal)
                    {
                        /* http://www.google.com/fusiontables/DataSource?dsrcid=423292 */
                        char** papszTokens2 = CSLTokenizeString2(pszVal, " ,", 0);
                        if (CSLCount(papszTokens2) == 2 &&
                            CPLGetValueType(papszTokens2[0]) == CPL_VALUE_REAL &&
                            CPLGetValueType(papszTokens2[1]) == CPL_VALUE_REAL &&
                            fabs(CPLAtof(papszTokens2[0])) <= 90 &&
                            fabs(CPLAtof(papszTokens2[1])) <= 180 )
                        {
                            if (iGeometryField < 0)
                            {
                                iGeometryField = i;
                                eType = wkbPoint;
                            }
                            else
                            {
                                CPLDebug("GFT", "Multiple geometry fields detected. "
                                         "Only first encountered one is handled");
                            }
                        }
                        CSLDestroy(papszTokens2);
                    }
                }
            }
            CSLDestroy(papszTokens);
        }

        if (iGeometryField < 0)
            poFeatureDefn->SetGeomType( wkbNone );
        else
            poFeatureDefn->SetGeomType( eType );
    }

    SetGeomFieldName();

    return TRUE;
}

/************************************************************************/
/*                         EscapeAndQuote()                             */
/************************************************************************/

static CPLString EscapeAndQuote(const char* pszStr)
{
    char ch = '\0';
    CPLString osRes("'");
    while((ch = *pszStr) != 0)
    {
        if (ch == '\'')
            osRes += "\\\'";
        else
            osRes += ch;
        pszStr ++;
    }
    osRes += "'";
    return osRes;
}

/************************************************************************/
/*                           FetchNextRows()                            */
/************************************************************************/

int OGRGFTTableLayer::FetchNextRows()
{
    aosRows.resize(0);

    CPLString osSQL("SELECT ROWID");
    for(int i=0;i<poFeatureDefn->GetFieldCount();i++)
    {
        osSQL += ",";

        if (i < (int)aosColumnInternalName.size())
            osSQL += aosColumnInternalName[i];
        else
        {
            const char* pszFieldName =
                poFeatureDefn->GetFieldDefn(i)->GetNameRef();
            osSQL += EscapeAndQuote(pszFieldName);
        }
    }
    if (bHiddenGeometryField)
    {
        osSQL += ",";
        osSQL += EscapeAndQuote(GetGeometryColumn());
    }
    osSQL += " FROM ";
    osSQL += osTableId;
    if (!osWHERE.empty() )
    {
        osSQL += " ";
        osSQL += osWHERE;
    }

    int nFeaturesToFetch = GetFeaturesToFetch();
    if (nFeaturesToFetch > 0)
        osSQL += CPLSPrintf(" OFFSET %d LIMIT %d", nOffset, nFeaturesToFetch);

    CPLPushErrorHandler(CPLQuietErrorHandler);
    CPLHTTPResult * psResult = poDS->RunSQL(osSQL);
    CPLPopErrorHandler();

    if (psResult == nullptr)
    {
        bEOF = TRUE;
        return FALSE;
    }

    char* pszLine = (char*) psResult->pabyData;
    if (pszLine == nullptr || psResult->pszErrBuf != nullptr)
    {
        CPLDebug("GFT", "Error : %s", pszLine ? pszLine : psResult->pszErrBuf);
        CPLHTTPDestroyResult(psResult);
        bEOF = TRUE;
        return FALSE;
    }

    ParseCSVResponse(pszLine, aosRows);

    if (!aosRows.empty())
        aosRows.erase(aosRows.begin());

    if (nFeaturesToFetch > 0)
        bEOF = (int)aosRows.size() < GetFeaturesToFetch();
    else
        bEOF = TRUE;

    CPLHTTPDestroyResult(psResult);

    return TRUE;
}
/************************************************************************/
/*                            GetFeature()                              */
/************************************************************************/

OGRFeature * OGRGFTTableLayer::GetFeature( GIntBig nFID )
{
    GetLayerDefn();

    CPLString osSQL("SELECT ROWID");
    for(int i=0;i<poFeatureDefn->GetFieldCount();i++)
    {
        osSQL += ",";

        const char* pszFieldName =
            poFeatureDefn->GetFieldDefn(i)->GetNameRef();
        osSQL += EscapeAndQuote(pszFieldName);
    }
    if (bHiddenGeometryField)
    {
        osSQL += ",";
        osSQL += EscapeAndQuote(GetGeometryColumn());
    }
    osSQL += " FROM ";
    osSQL += osTableId;
    osSQL += CPLSPrintf(" WHERE ROWID='" CPL_FRMT_GIB "'", nFID);

    CPLPushErrorHandler(CPLQuietErrorHandler);
    CPLHTTPResult * psResult = poDS->RunSQL(osSQL);
    CPLPopErrorHandler();

    if (psResult == nullptr)
        return nullptr;

    char* pszLine = (char*) psResult->pabyData;
    if (pszLine == nullptr || psResult->pszErrBuf != nullptr)
    {
        CPLHTTPDestroyResult(psResult);
        return nullptr;
    }

    /* skip header line */
    pszLine = OGRGFTGotoNextLine(pszLine);
    if (pszLine == nullptr || pszLine[0] == 0)
    {
        CPLHTTPDestroyResult(psResult);
        return nullptr;
    }

    int nLen = (int)strlen(pszLine);
    if (nLen > 0 && pszLine[nLen-1] == '\n')
        pszLine[nLen-1] = '\0';

    OGRFeature* poFeature = BuildFeatureFromSQL(pszLine);

    CPLHTTPDestroyResult(psResult);

    return poFeature;
}

/************************************************************************/
/*                           GetLayerDefn()                             */
/************************************************************************/

OGRFeatureDefn * OGRGFTTableLayer::GetLayerDefn()
{
    if (poFeatureDefn == nullptr)
    {
        if (osTableId.empty())
            return nullptr;
        FetchDescribe();
    }

    return poFeatureDefn;
}

/************************************************************************/
/*                          GetFeatureCount()                           */
/************************************************************************/

GIntBig OGRGFTTableLayer::GetFeatureCount(CPL_UNUSED int bForce)
{
    GetLayerDefn();

    CPLString osSQL("SELECT COUNT() FROM ");
    osSQL += osTableId;
    if (!osWHERE.empty() )
    {
        osSQL += " ";
        osSQL += osWHERE;
    }

    CPLHTTPResult * psResult = poDS->RunSQL(osSQL);

    if (psResult == nullptr)
        return 0;

    char* pszLine = (char*) psResult->pabyData;
    if (pszLine == nullptr ||
        !STARTS_WITH(pszLine, "count()") ||
        psResult->pszErrBuf != nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "GetFeatureCount() failed");
        CPLHTTPDestroyResult(psResult);
        return 0;
    }

    pszLine = OGRGFTGotoNextLine(pszLine);
    if (pszLine == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "GetFeatureCount() failed");
        CPLHTTPDestroyResult(psResult);
        return 0;
    }

    char* pszNextLine = OGRGFTGotoNextLine(pszLine);
    if (pszNextLine)
        pszNextLine[-1] = 0;

    int nFeatureCount = atoi(pszLine);

    CPLHTTPDestroyResult(psResult);

    return nFeatureCount;
}

/************************************************************************/
/*                            CreateField()                             */
/************************************************************************/

OGRErr OGRGFTTableLayer::CreateField( OGRFieldDefn *poField,
                                      CPL_UNUSED int bApproxOK )
{
    if (!poDS->IsReadWrite())
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Operation not available in read-only mode");
        return OGRERR_FAILURE;
    }

    if (!osTableId.empty())
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Cannot add field to already created table");
        return OGRERR_FAILURE;
    }

    if (poDS->GetAccessToken().empty())
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Operation not available in unauthenticated mode");
        return OGRERR_FAILURE;
    }

    poFeatureDefn->AddFieldDefn(poField);

    return OGRERR_NONE;
}

/************************************************************************/
/*                       CreateTableIfNecessary()                       */
/************************************************************************/

void OGRGFTTableLayer::CreateTableIfNecessary()
{
    if (bHasTriedCreateTable || !osTableId.empty())
        return;

    bHasTriedCreateTable = TRUE;

    CPLString osSQL("CREATE TABLE '");
    osSQL += osTableName;
    osSQL += "' (";

    /* If there are longitude and latitude fields, use the latitude */
    /* field as the LOCATION field */
    for( int i=0; i < poFeatureDefn->GetFieldCount(); i++ )
    {
        const char* pszName = poFeatureDefn->GetFieldDefn(i)->GetNameRef();
        if (EQUAL(pszName, "latitude") || EQUAL(pszName, "lat") ||
            EQUAL(pszName, "latdec"))
            iLatitudeField = i;
        else if (EQUAL(pszName, "longitude") || EQUAL(pszName, "lon") ||
                 EQUAL(pszName, "londec") || EQUAL(pszName, "long"))
            iLongitudeField = i;
    }

    if (iLatitudeField >= 0 && iLongitudeField >= 0)
    {
        iGeometryField = iLatitudeField;
        poFeatureDefn->SetGeomType( wkbPoint );
    }
    /* If no longitude/latitude field exist, let's look at a column */
    /* named 'geometry' and use it as the LOCATION column if the layer */
    /* hasn't been created with a none geometry type */
    else if (iGeometryField < 0 && eGTypeForCreation != wkbNone)
    {
        iGeometryField = poFeatureDefn->GetFieldIndex(GetDefaultGeometryColumnName());
        poFeatureDefn->SetGeomType( eGTypeForCreation );
    }
    /* The user doesn't want geometries, so don't create one */
    else if (eGTypeForCreation == wkbNone)
    {
        poFeatureDefn->SetGeomType( eGTypeForCreation );
    }

    int i = 0;
    for( ; i < poFeatureDefn->GetFieldCount(); i++ )
    {
        if (i > 0)
            osSQL += ", ";

        const char* pszFieldName =
            poFeatureDefn->GetFieldDefn(i)->GetNameRef();
        osSQL += EscapeAndQuote(pszFieldName);
        osSQL += ": ";

        if (iGeometryField == i)
        {
            osSQL += "LOCATION";
        }
        else
        {
            switch(poFeatureDefn->GetFieldDefn(i)->GetType())
            {
                case OFTInteger:
                case OFTReal:
                    osSQL += "NUMBER";
                    break;
                default:
                    osSQL += "STRING";
            }
        }
    }

    /* If there's not yet a geometry field and the user didn't forbid */
    /* the creation of one, then let's add it to the CREATE TABLE, but */
    /* DO NOT add it to the feature defn as a feature might already have */
    /* been created with it, so it is not safe to alter it at that point. */
    /* So we set the bHiddenGeometryField flag to be able to fetch/set this */
    /* column but not try to get/set a related feature field */
    if (iGeometryField < 0 && eGTypeForCreation != wkbNone)
    {
        if (i > 0)
            osSQL += ", ";
        osSQL += EscapeAndQuote(GetDefaultGeometryColumnName());
        osSQL += ": LOCATION";

        iGeometryField = poFeatureDefn->GetFieldCount();
        bHiddenGeometryField = TRUE;
    }
    osSQL += ")";

    CPLHTTPResult * psResult = poDS->RunSQL(osSQL);
    if (psResult == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Table creation failed");
        return;
    }

    char* pszLine = (char*) psResult->pabyData;
    if (pszLine == nullptr ||
        !STARTS_WITH(pszLine, "tableid") ||
        psResult->pszErrBuf != nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Table creation failed");
        CPLHTTPDestroyResult(psResult);
        return;
    }

    pszLine = OGRGFTGotoNextLine(pszLine);
    if (pszLine == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Table creation failed");
        CPLHTTPDestroyResult(psResult);
        return;
    }

    char* pszNextLine = OGRGFTGotoNextLine(pszLine);
    if (pszNextLine)
        pszNextLine[-1] = 0;

    osTableId = pszLine;
    CPLDebug("GFT", "Table %s --> id = %s", osTableName.c_str(), osTableId.c_str());

    CPLHTTPDestroyResult(psResult);
}

/************************************************************************/
/*                           ICreateFeature()                            */
/************************************************************************/

OGRErr OGRGFTTableLayer::ICreateFeature( OGRFeature *poFeature )

{
    if (!poDS->IsReadWrite())
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Operation not available in read-only mode");
        return OGRERR_FAILURE;
    }

    if (osTableId.empty())
    {
        CreateTableIfNecessary();
        if (osTableId.empty())
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                    "Cannot add feature to non-created table");
            return OGRERR_FAILURE;
        }
    }

    if (poDS->GetAccessToken().empty())
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Operation not available in unauthenticated mode");
        return OGRERR_FAILURE;
    }

    CPLString osCommand = "INSERT INTO ";
    osCommand += osTableId;
    osCommand += " (";

    const int nFieldCount = poFeatureDefn->GetFieldCount();
    int iField = 0;  // Used after for
    for( ; iField < nFieldCount; iField++ )
    {
        if (iField > 0)
            osCommand += ", ";

        const char* pszFieldName =
            poFeatureDefn->GetFieldDefn(iField)->GetNameRef();
        osCommand += EscapeAndQuote(pszFieldName);
    }
    if (bHiddenGeometryField)
    {
        if (iField > 0)
            osCommand += ", ";
        osCommand += EscapeAndQuote(GetGeometryColumn());
    }
    osCommand += ") VALUES (";
    for(iField = 0; iField < nFieldCount + bHiddenGeometryField; iField++)
    {
        if (iField > 0)
            osCommand += ", ";

        OGRGeometry* poGeom = poFeature->GetGeometryRef();
        /* If there's a geometry, let's use it in priority over the textual */
        /* content of the field. */
        if (iGeometryField != iLatitudeField && iField == iGeometryField &&
            (iField == nFieldCount || poGeom != nullptr || !poFeature->IsFieldSetAndNotNull( iField )))
        {
            if (poGeom == nullptr)
                osCommand += "''";
            else
            {
                char* pszKML = nullptr;
                if (poGeom->getSpatialReference() != nullptr &&
                    !poGeom->getSpatialReference()->IsSame(poSRS))
                {
                    OGRGeometry* poGeom4326 = poGeom->clone();
                    poGeom4326->transformTo(poSRS);
                    pszKML = poGeom4326->exportToKML();
                    delete poGeom4326;
                }
                else
                {
                    pszKML = poGeom->exportToKML();
                }
                osCommand += "'";
                osCommand += pszKML;
                osCommand += "'";
                CPLFree(pszKML);
            }
            continue;
        }

        if( !poFeature->IsFieldSetAndNotNull( iField ) )
        {
            osCommand += "''";
        }
        else
        {
            OGRFieldType eType = poFeatureDefn->GetFieldDefn(iField)->GetType();
            if (eType != OFTInteger && eType != OFTReal)
            {
                CPLString osTmp;
                const char* pszVal = poFeature->GetFieldAsString(iField);

                if (!CPLIsUTF8(pszVal, -1))
                {
                    static int bFirstTime = TRUE;
                    if (bFirstTime)
                    {
                        bFirstTime = FALSE;
                        CPLError(CE_Warning, CPLE_AppDefined,
                                "%s is not a valid UTF-8 string. Forcing it to ASCII.\n"
                                "This warning won't be issued anymore", pszVal);
                    }
                    else
                    {
                        CPLDebug("OGR", "%s is not a valid UTF-8 string. Forcing it to ASCII",
                                pszVal);
                    }
                    char* pszEscaped = CPLForceToASCII(pszVal, -1, '?');
                    osTmp = pszEscaped;
                    CPLFree(pszEscaped);
                    pszVal = osTmp.c_str();
                }

                osCommand += EscapeAndQuote(pszVal);
            }
            else
                osCommand += poFeature->GetFieldAsString(iField);
        }
    }

    osCommand += ")";

    //CPLDebug("GFT", "%s",  osCommand.c_str());

    if (bInTransaction)
    {
        nFeaturesInTransaction ++;
        if (nFeaturesInTransaction > 1)
            osTransaction += "; ";
        osTransaction += osCommand;
        return OGRERR_NONE;
    }

    CPLHTTPResult * psResult = poDS->RunSQL(osCommand);
    if (psResult == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Feature creation failed");
        return OGRERR_FAILURE;
    }

    char* pszLine = (char*) psResult->pabyData;
    if (pszLine == nullptr ||
        !STARTS_WITH(pszLine, "rowid") ||
        psResult->pszErrBuf != nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Feature creation failed");
        CPLHTTPDestroyResult(psResult);
        return OGRERR_FAILURE;
    }

    pszLine = OGRGFTGotoNextLine(pszLine);
    if (pszLine == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Feature creation failed");
        CPLHTTPDestroyResult(psResult);
        return OGRERR_FAILURE;
    }

    char* pszNextLine = OGRGFTGotoNextLine(pszLine);
    if (pszNextLine)
        pszNextLine[-1] = 0;

    CPLDebug("GFT", "Feature id = %s",  pszLine);

    int nFID = atoi(pszLine);
    if (strcmp(CPLSPrintf("%d", nFID), pszLine) == 0)
        poFeature->SetFID(nFID);

    CPLHTTPDestroyResult(psResult);

    return OGRERR_NONE;
}

/************************************************************************/
/*                           ISetFeature()                               */
/************************************************************************/

OGRErr      OGRGFTTableLayer::ISetFeature( OGRFeature *poFeature )
{
    GetLayerDefn();

    if (!poDS->IsReadWrite())
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Operation not available in read-only mode");
        return OGRERR_FAILURE;
    }

    if (osTableId.empty())
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                "Cannot set feature to non-created table");
        return OGRERR_FAILURE;
    }

    if (poDS->GetAccessToken().empty())
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Operation not available in unauthenticated mode");
        return OGRERR_FAILURE;
    }

    if (poFeature->GetFID() == OGRNullFID)
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "FID required on features given to SetFeature()." );
        return OGRERR_FAILURE;
    }

    CPLString osCommand = "UPDATE ";
    osCommand += osTableId;
    osCommand += " SET ";

    const int nFieldCount = poFeatureDefn->GetFieldCount();
    for( int iField = 0; iField < nFieldCount + bHiddenGeometryField; iField++)
    {
        if (iField > 0)
            osCommand += ", ";

        if (iField == nFieldCount)
        {
            osCommand += EscapeAndQuote(GetGeometryColumn());
        }
        else
        {
            const char* pszFieldName =
                poFeatureDefn->GetFieldDefn(iField)->GetNameRef();
            osCommand += EscapeAndQuote(pszFieldName);
        }

        osCommand += " = ";

        OGRGeometry* poGeom = poFeature->GetGeometryRef();
        if (iGeometryField != iLatitudeField && iField == iGeometryField &&
            (iField == nFieldCount || poGeom != nullptr || !poFeature->IsFieldSetAndNotNull( iField )))
        {
            if (poGeom == nullptr)
                osCommand += "''";
            else
            {
                char* pszKML = nullptr;
                if (poGeom->getSpatialReference() != nullptr &&
                    !poGeom->getSpatialReference()->IsSame(poSRS))
                {
                    OGRGeometry* poGeom4326 = poGeom->clone();
                    poGeom4326->transformTo(poSRS);
                    pszKML = poGeom4326->exportToKML();
                    delete poGeom4326;
                }
                else
                {
                    pszKML = poGeom->exportToKML();
                }
                osCommand += "'";
                osCommand += pszKML;
                osCommand += "'";
                CPLFree(pszKML);
            }
            continue;
        }

        if( !poFeature->IsFieldSetAndNotNull( iField ) )
        {
            osCommand += "''";
        }
        else
        {
            OGRFieldType eType = poFeatureDefn->GetFieldDefn(iField)->GetType();
            if (eType != OFTInteger && eType != OFTReal)
            {
                CPLString osTmp;
                const char* pszVal = poFeature->GetFieldAsString(iField);

                if (!CPLIsUTF8(pszVal, -1))
                {
                    static int bFirstTime = TRUE;
                    if (bFirstTime)
                    {
                        bFirstTime = FALSE;
                        CPLError(CE_Warning, CPLE_AppDefined,
                                "%s is not a valid UTF-8 string. Forcing it to ASCII.\n"
                                "This warning won't be issued anymore", pszVal);
                    }
                    else
                    {
                        CPLDebug("OGR", "%s is not a valid UTF-8 string. Forcing it to ASCII",
                                pszVal);
                    }
                    char* pszEscaped = CPLForceToASCII(pszVal, -1, '?');
                    osTmp = pszEscaped;
                    CPLFree(pszEscaped);
                    pszVal = osTmp.c_str();
                }

                osCommand += EscapeAndQuote(pszVal);
            }
            else
                osCommand += poFeature->GetFieldAsString(iField);
        }
    }

    osCommand += " WHERE ROWID = '";
    osCommand += CPLSPrintf(CPL_FRMT_GIB, poFeature->GetFID());
    osCommand += "'";

    CPLHTTPResult * psResult = poDS->RunSQL(osCommand);
    if (psResult == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Feature update failed (1)");
        return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      We expect a response like "affected_rows\n1".                   */
/* -------------------------------------------------------------------- */
    char* pszLine = (char*) psResult->pabyData;
    if (pszLine == nullptr ||
        !STARTS_WITH(pszLine, "affected_rows\n1\n") ||
        psResult->pszErrBuf != nullptr)
    {
        CPLDebug( "GFT", "%s/%s",
                  pszLine ? pszLine : "null",
                  psResult->pszErrBuf ? psResult->pszErrBuf : "null");
        CPLError(CE_Failure, CPLE_AppDefined, "Feature update failed (2)");
        CPLHTTPDestroyResult(psResult);
        return OGRERR_FAILURE;
    }

    CPLHTTPDestroyResult(psResult);

    return OGRERR_NONE;
}

/************************************************************************/
/*                          DeleteFeature()                             */
/************************************************************************/

OGRErr OGRGFTTableLayer::DeleteFeature( GIntBig nFID )
{
    GetLayerDefn();

    if (!poDS->IsReadWrite())
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Operation not available in read-only mode");
        return OGRERR_FAILURE;
    }

    if (osTableId.empty())
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                "Cannot delete feature in non-created table");
        return OGRERR_FAILURE;
    }

    if (poDS->GetAccessToken().empty())
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Operation not available in unauthenticated mode");
        return OGRERR_FAILURE;
    }

    CPLString      osCommand;

    osCommand += "DELETE FROM ";
    osCommand += osTableId;
    osCommand += " WHERE ROWID = '";
    osCommand += CPLSPrintf(CPL_FRMT_GIB, nFID);
    osCommand += "'";

    //CPLDebug("GFT", "%s",  osCommand.c_str());

    CPLHTTPResult * psResult = poDS->RunSQL(osCommand);
    if (psResult == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Feature deletion failed (1)");
        return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      We expect a response like "affected_rows\n1".                   */
/* -------------------------------------------------------------------- */
    char* pszLine = (char*) psResult->pabyData;
    if (pszLine == nullptr ||
        !STARTS_WITH(pszLine, "affected_rows\n1\n") ||
        psResult->pszErrBuf != nullptr)
    {
        CPLDebug( "GFT", "%s/%s",
                  pszLine ? pszLine : "null",
                  psResult->pszErrBuf ? psResult->pszErrBuf : "null");
        CPLError(CE_Failure, CPLE_AppDefined, "Feature deletion failed (2)");
        CPLHTTPDestroyResult(psResult);
        return OGRERR_FAILURE;
    }

    CPLHTTPDestroyResult(psResult);

    return OGRERR_NONE;
}

/************************************************************************/
/*                         StartTransaction()                           */
/************************************************************************/

OGRErr OGRGFTTableLayer::StartTransaction()
{
    GetLayerDefn();

    if (bInTransaction)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Already in transaction");
        return OGRERR_FAILURE;
    }

    if (!poDS->IsReadWrite())
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Operation not available in read-only mode");
        return OGRERR_FAILURE;
    }

    if (osTableId.empty())
    {
        CreateTableIfNecessary();
        if (osTableId.empty())
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                    "Cannot add feature to non-created table");
            return OGRERR_FAILURE;
        }
    }

    if (poDS->GetAccessToken().empty())
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Operation not available in unauthenticated mode");
        return OGRERR_FAILURE;
    }

    bInTransaction = TRUE;
    osTransaction.resize(0);
    nFeaturesInTransaction = 0;

    return OGRERR_NONE;
}

/************************************************************************/
/*                         CommitTransaction()                          */
/************************************************************************/

OGRErr OGRGFTTableLayer::CommitTransaction()
{
    GetLayerDefn();

    if (!bInTransaction)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Should be in transaction");
        return OGRERR_FAILURE;
    }

    bInTransaction = FALSE;

    if (nFeaturesInTransaction > 0)
    {
        if (nFeaturesInTransaction > 1)
            osTransaction += ";";

        CPLHTTPResult * psResult = poDS->RunSQL(osTransaction);
        osTransaction.resize(0);
        nFeaturesInTransaction = 0;

        if (psResult == nullptr)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "CommitTransaction failed");
            return OGRERR_FAILURE;
        }

        char* pszLine = (char*) psResult->pabyData;
        if (pszLine == nullptr ||
            !STARTS_WITH(pszLine, "rowid") ||
            psResult->pszErrBuf != nullptr)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "CommitTransaction failed : %s",
                     pszLine ? pszLine : psResult->pszErrBuf);
            CPLHTTPDestroyResult(psResult);
            return OGRERR_FAILURE;
        }

        pszLine = OGRGFTGotoNextLine(pszLine);
        while(pszLine && *pszLine != 0)
        {
            char* pszNextLine = OGRGFTGotoNextLine(pszLine);
            if (pszNextLine)
                pszNextLine[-1] = 0;
            //CPLDebug("GFT", "Feature id = %s",  pszLine);

            pszLine = pszNextLine;
        }

        CPLHTTPDestroyResult(psResult);
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                        RollbackTransaction()                         */
/************************************************************************/

OGRErr OGRGFTTableLayer::RollbackTransaction()
{
    GetLayerDefn();

    if (!bInTransaction)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Should be in transaction");
        return OGRERR_FAILURE;
    }
    bInTransaction = FALSE;
    nFeaturesInTransaction = 0;
    osTransaction.resize(0);
    return OGRERR_NONE;
}

/************************************************************************/
/*                         SetAttributeFilter()                         */
/************************************************************************/

OGRErr OGRGFTTableLayer::SetAttributeFilter( const char *pszQuery )

{
    GetLayerDefn();

    if( pszQuery == nullptr )
        osQuery = "";
    else
    {
        osQuery = PatchSQL(pszQuery);
    }

    BuildWhere();

    ResetReading();

    return OGRERR_NONE;
}

/************************************************************************/
/*                          SetSpatialFilter()                          */
/************************************************************************/

void OGRGFTTableLayer::SetSpatialFilter( OGRGeometry * poGeomIn )

{
    GetLayerDefn();

    if( InstallFilter( poGeomIn ) )
    {
        BuildWhere();

        ResetReading();
    }
}

/************************************************************************/
/*                             BuildWhere()                             */
/*                                                                      */
/*      Build the WHERE statement appropriate to the current set of     */
/*      criteria (spatial and attribute queries).                       */
/************************************************************************/

void OGRGFTTableLayer::BuildWhere()

{
    osWHERE = "";

    if( m_poFilterGeom != nullptr && iGeometryField >= 0)
    {
        OGREnvelope  sEnvelope;

        m_poFilterGeom->getEnvelope( &sEnvelope );

        CPLString osQuotedGeomColumn(EscapeAndQuote(GetGeometryColumn()));

        osWHERE.Printf(
            "WHERE ST_INTERSECTS(%s, "
            "RECTANGLE(LATLNG(%.12f, %.12f), LATLNG(%.12f, %.12f)))",
            osQuotedGeomColumn.c_str(),
            std::max(-90.0, sEnvelope.MinY - 1.0e-11),
            std::max(-180.0, sEnvelope.MinX - 1.0e-11),
            std::min(90.0, sEnvelope.MaxY + 1.0e-11),
            std::min(180.0, sEnvelope.MaxX + 1.0e-11));
    }

    if( !osQuery.empty() )
    {
        if( osWHERE.empty() )
            osWHERE = "WHERE ";
        else
            osWHERE += " AND ";
        osWHERE += osQuery;
    }
}

/************************************************************************/
/*                          SetGeometryType()                           */
/************************************************************************/

void OGRGFTTableLayer::SetGeometryType(OGRwkbGeometryType eGType)
{
    eGTypeForCreation = eGType;
}
