/******************************************************************************
 *
 * Project:  Oracle Spatial Driver
 * Purpose:  Implementation of the OGROCITableLayer class.  This class provides
 *           layer semantics on a table, but utilizing a lot of machinery from
 *           the OGROCILayer base class.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2002, Frank Warmerdam <warmerdam@pobox.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 "ogr_oci.h"
#include "cpl_conv.h"
#include "cpl_string.h"

CPL_CVSID("$Id: ogrocitablelayer.cpp c368bc9fdb1a0067d4400be15c08501137dab367 2018-05-18 05:10:17Z Ryan Oliver $")

static int nDiscarded = 0;
static int nHits = 0;

#define HSI_UNKNOWN  -2

/************************************************************************/
/*                          OGROCITableLayer()                          */
/************************************************************************/

OGROCITableLayer::OGROCITableLayer( OGROCIDataSource *poDSIn,
                                    const char * pszTableName, OGRwkbGeometryType eGType,
                                    int nSRIDIn, int bUpdate, int bNewLayerIn )

{
    poDS = poDSIn;
    bExtentUpdated = false;

    pszQuery = nullptr;
    pszWHERE = CPLStrdup( "" );
    pszQueryStatement = nullptr;

    bUpdateAccess = bUpdate;
    bNewLayer = bNewLayerIn;

    iNextShapeId = 0;
    iNextFIDToWrite = -1;

    bValidTable = FALSE;
    if( bNewLayerIn )
        bHaveSpatialIndex = FALSE;
    else
        bHaveSpatialIndex = HSI_UNKNOWN;

    poFeatureDefn = ReadTableDefinition( pszTableName );
    if( eGType != wkbUnknown && poFeatureDefn->GetGeomFieldCount() > 0 )
        poFeatureDefn->GetGeomFieldDefn(0)->SetType(eGType);
    SetDescription( poFeatureDefn->GetName() );

    nSRID = nSRIDIn;
    if( nSRID == -1 )
        nSRID = LookupTableSRID();

    poSRS = poDSIn->FetchSRS( nSRID );
    if( poSRS != nullptr )
        poSRS->Reference();

    hOrdVARRAY = nullptr;
    hElemInfoVARRAY = nullptr;

    poBoundStatement = nullptr;

    nWriteCacheMax = 0;
    nWriteCacheUsed = 0;
    pasWriteGeoms = nullptr;
    papsWriteGeomMap = nullptr;
    pasWriteGeomInd = nullptr;
    papsWriteGeomIndMap = nullptr;

    papWriteFields = nullptr;
    papaeWriteFieldInd = nullptr;

    panWriteFIDs = nullptr;

    nDefaultStringSize = 4000;

    OGROCITableLayer::ResetReading();
}

/************************************************************************/
/*                         ~OGROCITableLayer()                          */
/************************************************************************/

OGROCITableLayer::~OGROCITableLayer()

{
    int   i;

    OGROCITableLayer::SyncToDisk();

    CPLFree( panWriteFIDs );
    if( papWriteFields != nullptr )
    {
        for( i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
        {
            CPLFree( papWriteFields[i] );
            CPLFree( papaeWriteFieldInd[i] );
        }
    }

    CPLFree( papWriteFields );
    CPLFree( papaeWriteFieldInd );

    if( poBoundStatement != nullptr )
        delete poBoundStatement;

    CPLFree( pasWriteGeomInd );
    CPLFree( papsWriteGeomIndMap );

    CPLFree( papsWriteGeomMap );
    CPLFree( pasWriteGeoms );

    CPLFree( pszQuery );
    CPLFree( pszWHERE );

    if( poSRS != nullptr && poSRS->Dereference() == 0 )
        delete poSRS;
}

/************************************************************************/
/*                        ReadTableDefinition()                         */
/*                                                                      */
/*      Build a schema from the named table.  Done by querying the      */
/*      catalog.                                                        */
/************************************************************************/

OGRFeatureDefn *OGROCITableLayer::ReadTableDefinition( const char * pszTable )

{
    OGROCISession      *poSession = poDS->GetSession();
    sword               nStatus;

    CPLString osUnquotedTableName;
    CPLString osQuotedTableName;

/* -------------------------------------------------------------------- */
/*      Split out the owner if available.                               */
/* -------------------------------------------------------------------- */
    if( strstr(pszTable,".") != nullptr )
    {
        osTableName = strstr(pszTable,".") + 1;
        osOwner.assign( pszTable, strlen(pszTable)-osTableName.size() - 1 );
        osUnquotedTableName.Printf( "%s.%s", osOwner.c_str(), osTableName.c_str() );
        osQuotedTableName.Printf( "\"%s\".\"%s\"", osOwner.c_str(), osTableName.c_str() );
    }
    else
    {
        osTableName = pszTable;
        osOwner = "";
        osUnquotedTableName.Printf( "%s", pszTable );
        osQuotedTableName.Printf( "\"%s\"", pszTable );
    }

    OGRFeatureDefn *poDefn = new OGRFeatureDefn( osUnquotedTableName.c_str() );

    poDefn->Reference();

/* -------------------------------------------------------------------- */
/*      Do a DescribeAll on the table.                                  */
/* -------------------------------------------------------------------- */
    OCIParam *hAttrParam = nullptr;
    OCIParam *hAttrList = nullptr;

    // Table name unquoted

    nStatus =
        OCIDescribeAny( poSession->hSvcCtx, poSession->hError,
                        (dvoid *) osUnquotedTableName.c_str(),
                        static_cast<ub4>(osUnquotedTableName.length()), OCI_OTYPE_NAME,
                        OCI_DEFAULT, OCI_PTYPE_TABLE, poSession->hDescribe );

    if( poSession->Failed( nStatus, "OCIDescribeAny" ) )
    {
        CPLErrorReset();

        // View name unquoted

        nStatus =
            OCIDescribeAny(poSession->hSvcCtx, poSession->hError,
                           (dvoid *) osUnquotedTableName.c_str(),
                           static_cast<ub4>(osUnquotedTableName.length()), OCI_OTYPE_NAME,
                           OCI_DEFAULT, OCI_PTYPE_VIEW, poSession->hDescribe );

        if( poSession->Failed( nStatus, "OCIDescribeAny" ) )
        {
            CPLErrorReset();

            // Table name quoted

            nStatus =
                OCIDescribeAny( poSession->hSvcCtx, poSession->hError,
                                (dvoid *) osQuotedTableName.c_str(),
                                static_cast<ub4>(osQuotedTableName.length()), OCI_OTYPE_NAME,
                                OCI_DEFAULT, OCI_PTYPE_TABLE, poSession->hDescribe );

            if( poSession->Failed( nStatus, "OCIDescribeAny" ) )
            {
                CPLErrorReset();

                // View name quoted

                nStatus =
                    OCIDescribeAny(poSession->hSvcCtx, poSession->hError,
                                   (dvoid *) osQuotedTableName.c_str(),
                                   static_cast<ub4>(osQuotedTableName.length()), OCI_OTYPE_NAME,
                                   OCI_DEFAULT, OCI_PTYPE_VIEW, poSession->hDescribe );

                if( poSession->Failed( nStatus, "OCIDescribeAny" ) )
                    return poDefn;
            }
        }
    }

    if( poSession->Failed(
        OCIAttrGet( poSession->hDescribe, OCI_HTYPE_DESCRIBE,
                    &hAttrParam, nullptr, OCI_ATTR_PARAM, poSession->hError ),
        "OCIAttrGet(ATTR_PARAM)") )
        return poDefn;

    if( poSession->Failed(
        OCIAttrGet( hAttrParam, OCI_DTYPE_PARAM, &hAttrList, nullptr,
                    OCI_ATTR_LIST_COLUMNS, poSession->hError ),
        "OCIAttrGet(ATTR_LIST_COLUMNS)" ) )
        return poDefn;

/* -------------------------------------------------------------------- */
/*      What is the name of the column to use as FID?  This defaults    */
/*      to OGR_FID but we allow it to be overridden by a config         */
/*      variable.  Ideally we would identify a column that is a         */
/*      primary key and use that, but I'm not yet sure how to           */
/*      accomplish that.                                                */
/* -------------------------------------------------------------------- */
    const char *pszExpectedFIDName =
        CPLGetConfigOption( "OCI_FID", "OGR_FID" );
    int bGeomFieldNullable = FALSE;

/* -------------------------------------------------------------------- */
/*      Parse the returned table information.                           */
/* -------------------------------------------------------------------- */
    for( int iRawFld = 0; true; iRawFld++ )
    {
        OGRFieldDefn    oField( "", OFTString);
        OCIParam     *hParmDesc;
        ub2          nOCIType;
        ub4          nOCILen;

        nStatus = OCIParamGet( hAttrList, OCI_DTYPE_PARAM,
                               poSession->hError, (dvoid**)&hParmDesc,
                               (ub4) iRawFld+1 );
        if( nStatus != OCI_SUCCESS )
            break;

        if( poSession->GetParmInfo( hParmDesc, &oField, &nOCIType, &nOCILen )
            != CE_None )
            return poDefn;

        if( oField.GetType() == OFTBinary )
        {
            if( nOCIType == 108 && pszGeomName == nullptr )
            {
                CPLFree( pszGeomName );
                pszGeomName = CPLStrdup( oField.GetNameRef() );
                iGeomColumn = iRawFld;
                bGeomFieldNullable = oField.IsNullable();
            }
            continue;
        }

        if( EQUAL(oField.GetNameRef(),pszExpectedFIDName)
            && (oField.GetType() == OFTInteger ||
                oField.GetType() == OFTInteger64) )
        {
            pszFIDName = CPLStrdup(oField.GetNameRef());
            continue;
        }

        poDefn->AddFieldDefn( &oField );
    }

    OGROCIStatement defaultValuesStatement( poSession );
    
    const char* pszDefaultValueSQL =
        "SELECT COLUMN_NAME, DATA_DEFAULT\n"
        "FROM user_tab_columns\n"
        "WHERE DATA_DEFAULT IS NOT NULL AND TABLE_NAME = UPPER(:table_name)";

    defaultValuesStatement.Prepare(pszDefaultValueSQL);
    defaultValuesStatement.BindString(":table_name", pszTable);

    if( defaultValuesStatement.Execute( nullptr ) == CE_None )
    {
        char **papszRow;

        while( (papszRow = defaultValuesStatement.SimpleFetchRow()) != nullptr )
        {
            const char* pszColName = papszRow[0];
            const char* pszDefault = papszRow[1];
            int nIdx = poDefn->GetFieldIndex(pszColName);
            if( nIdx >= 0 )
                poDefn->GetFieldDefn(nIdx)->SetDefault(pszDefault);
        }
    }

    if( EQUAL(pszExpectedFIDName, "OGR_FID") && pszFIDName )
    {
        for(int i=0;i<poDefn->GetFieldCount();i++)
        {
            // This is presumably a Integer since we always create Integer64 with a
            // defined precision
            if( poDefn->GetFieldDefn(i)->GetType() == OFTInteger64 &&
                poDefn->GetFieldDefn(i)->GetWidth() == 0 )
            {
                poDefn->GetFieldDefn(i)->SetType(OFTInteger);
            }
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Identify Geometry dimension                                     */
    /* -------------------------------------------------------------------- */

    if( pszGeomName != nullptr && strlen(pszGeomName) > 0 )
    {
        OGROCIStatement oDimStatement( poSession );
        char **papszResult;
        int iDim = -1;

        if( osOwner != "" )
        {
            const char* pszDimCmdA =
                "SELECT COUNT(*)\n"
                "FROM ALL_SDO_GEOM_METADATA u, TABLE(u.diminfo) t\n"
                "WHERE u.table_name = :table_name\n"
                "  AND u.column_name = :geometry_name\n"
                "  AND u.owner = :table_owner";

            oDimStatement.Prepare( pszDimCmdA );
            oDimStatement.BindString( ":table_name", osTableName.c_str() );
            oDimStatement.BindString( ":geometry_name", pszGeomName );
            oDimStatement.BindString( ":table_owner", osOwner.c_str() );
        }
        else
        {
            const char* pszDimCmdB =
                "SELECT COUNT(*)\n"
                "FROM USER_SDO_GEOM_METADATA u, TABLE(u.diminfo) t\n"
                "WHERE u.table_name = :table_name\n"
                "  AND u.column_name = :geometry_name";

            oDimStatement.Prepare( pszDimCmdB );
            oDimStatement.BindString( ":table_name", osTableName.c_str() );
            oDimStatement.BindString( ":geometry_name", pszGeomName );
        }
        oDimStatement.Execute( nullptr );

        papszResult = oDimStatement.SimpleFetchRow();

        if( CSLCount(papszResult) < 1 )
        {
            OGROCIStatement oDimStatement2( poSession );
            char **papszResult2;

            CPLErrorReset();

            if( osOwner != "" )
            {
                const char* pszDimCmd2A =
                    "select m.sdo_index_dims\n"
                    "from   all_sdo_index_metadata m, all_sdo_index_info i\n"
                    "where  i.index_name = m.sdo_index_name\n"
                    "   and i.sdo_index_owner = m.sdo_index_owner\n"
                    "   and i.sdo_index_owner = upper(:table_owner)\n"
                    "   and i.table_name = upper(:table_name)";

                oDimStatement2.Prepare( pszDimCmd2A );
                oDimStatement2.BindString( ":table_owner", osOwner.c_str());
                oDimStatement2.BindString( ":table_name", osTableName.c_str());
            }
            else
            {
                const char* pszDimCmd2B =
                    "select m.sdo_index_dims\n"
                    "from   user_sdo_index_metadata m, user_sdo_index_info i\n"
                    "where  i.index_name = m.sdo_index_name\n"
                    "   and i.table_name = upper(:table_name)";

                oDimStatement2.Prepare( pszDimCmd2B );
                oDimStatement2.BindString( ":table_name", osTableName.c_str());
            }
            oDimStatement2.Execute( nullptr );

            papszResult2 = oDimStatement2.SimpleFetchRow();

            if( CSLCount( papszResult2 ) > 0 )
            {
                iDim = atoi( papszResult2[0] );
            }
            else
            {
                // we want to clear any errors to avoid confusing the application.
                CPLErrorReset();
            }
        }
        else
        {
            iDim = atoi( papszResult[0] );
        }

        if( iDim > 0 )
        {
            SetDimension( iDim );
        }
        else
        {
            CPLDebug( "OCI", "get dim based of existing data or index failed." );
        }

        {
            OGROCIStatement oDimStatement2( poSession );
            char **papszResult2;

            CPLErrorReset();
            if( osOwner != "" )
            {
                const char* pszLayerTypeCmdA =
                    "select m.SDO_LAYER_GTYPE "
                    "from all_sdo_index_metadata m, all_sdo_index_info i "
                    "where i.index_name = m.sdo_index_name "
                    "and i.sdo_index_owner = m.sdo_index_owner "
                    "and i.sdo_index_owner = upper(:table_owner) "
                    "and i.table_name = upper(:table_name)";

                oDimStatement2.Prepare( pszLayerTypeCmdA );
                oDimStatement2.BindString( ":table_owner", osOwner.c_str() );
                oDimStatement2.BindString( ":table_name", osTableName.c_str() );
            }
            else
            {
                const char* pszLayerTypeCmdB =
                    "select m.SDO_LAYER_GTYPE "
                    "from user_sdo_index_metadata m, user_sdo_index_info i "
                    "where i.index_name = m.sdo_index_name "
                    "and i.table_name = upper(:table_name)";
                oDimStatement2.Prepare( pszLayerTypeCmdB );
                oDimStatement2.BindString( ":table_name", osTableName.c_str() );
            }

            oDimStatement2.Execute( nullptr );

            papszResult2 = oDimStatement2.SimpleFetchRow();

            if( CSLCount( papszResult2 ) > 0 )
            {
                const char* pszLayerGType = papszResult2[0];
                OGRwkbGeometryType eGeomType = wkbUnknown;
                if( EQUAL(pszLayerGType, "POINT") )
                    eGeomType = wkbPoint;
                else if( EQUAL(pszLayerGType, "LINE") )
                    eGeomType = wkbLineString;
                else if( EQUAL(pszLayerGType, "POLYGON") )
                    eGeomType = wkbPolygon;
                else if( EQUAL(pszLayerGType, "MULTIPOINT") )
                    eGeomType = wkbMultiPoint;
                else if( EQUAL(pszLayerGType, "MULTILINE") )
                    eGeomType = wkbMultiLineString;
                else if( EQUAL(pszLayerGType, "MULTIPOLYGON") )
                    eGeomType = wkbMultiPolygon;
                else if( !EQUAL(pszLayerGType, "COLLECTION") )
                    CPLDebug("OCI", "LAYER_GTYPE = %s", pszLayerGType );
                if( iDim == 3 )
                    eGeomType = wkbSetZ(eGeomType);
                poDefn->GetGeomFieldDefn(0)->SetType( eGeomType );
                poDefn->GetGeomFieldDefn(0)->SetNullable( bGeomFieldNullable );
            }
            else
            {
                // we want to clear any errors to avoid confusing the application.
                CPLErrorReset();
            }
        }
    }
    else
    {
        poDefn->SetGeomType(wkbNone);
    }

    bValidTable = TRUE;

    return poDefn;
}

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

void OGROCITableLayer::SetSpatialFilter( OGRGeometry * poGeomIn )

{
    if( !InstallFilter( poGeomIn ) )
        return;

    BuildWhere();

    ResetReading();
}

/************************************************************************/
/*                        TestForSpatialIndex()                         */
/************************************************************************/

void OGROCITableLayer::TestForSpatialIndex( const char *pszSpatWHERE )

{
    OGROCIStringBuf oTestCmd;
    OGROCIStatement oTestStatement( poDS->GetSession() );

    oTestCmd.Append( "SELECT COUNT(*) FROM " );
    oTestCmd.Append( poFeatureDefn->GetName() );
    oTestCmd.Append( pszSpatWHERE );

    if( oTestStatement.Execute( oTestCmd.GetString() ) != CE_None )
        bHaveSpatialIndex = FALSE;
    else
        bHaveSpatialIndex = TRUE;
}

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

void OGROCITableLayer::BuildWhere()

{
    OGROCIStringBuf oWHERE;

    CPLFree( pszWHERE );
    pszWHERE = nullptr;

    if( m_poFilterGeom != nullptr && bHaveSpatialIndex )
    {
        OGREnvelope  sEnvelope;

        m_poFilterGeom->getEnvelope( &sEnvelope );

        oWHERE.Append( " WHERE sdo_filter(" );
        oWHERE.Append( pszGeomName );
        oWHERE.Append( ", MDSYS.SDO_GEOMETRY(2003," );
        if( nSRID == -1 )
            oWHERE.Append( "NULL" );
        else
            oWHERE.Appendf( 15, "%d", nSRID );
        oWHERE.Append( ",NULL," );
        oWHERE.Append( "MDSYS.SDO_ELEM_INFO_ARRAY(1,1003,1)," );
        oWHERE.Append( "MDSYS.SDO_ORDINATE_ARRAY(" );
        oWHERE.Appendf( 600,
                "%.16g,%.16g,%.16g,%.16g,%.16g,%.16g,%.16g,%.16g,%.16g,%.16g",
                        sEnvelope.MinX, sEnvelope.MinY,
                        sEnvelope.MaxX, sEnvelope.MinY,
                        sEnvelope.MaxX, sEnvelope.MaxY,
                        sEnvelope.MinX, sEnvelope.MaxY,
                        sEnvelope.MinX, sEnvelope.MinY);
        oWHERE.Append( ")), 'querytype=window') = 'TRUE' " );
    }

    if( bHaveSpatialIndex == HSI_UNKNOWN )
    {
        TestForSpatialIndex( oWHERE.GetString() );
        if( !bHaveSpatialIndex )
            oWHERE.Clear();
    }

    if( pszQuery != nullptr )
    {
        if( oWHERE.GetLast() == '\0' )
            oWHERE.Append( "WHERE " );
        else
            oWHERE.Append( "AND " );

        oWHERE.Append( pszQuery );
    }

    pszWHERE = oWHERE.StealString();
}

/************************************************************************/
/*                      BuildFullQueryStatement()                       */
/************************************************************************/

void OGROCITableLayer::BuildFullQueryStatement()

{
    if( pszQueryStatement != nullptr )
    {
        CPLFree( pszQueryStatement );
        pszQueryStatement = nullptr;
    }

    OGROCIStringBuf oCmd;
    char *pszFields = BuildFields();

    oCmd.Append( "SELECT " );
    oCmd.Append( pszFields );
    oCmd.Append( " FROM " );
    oCmd.Append( poFeatureDefn->GetName() );
    oCmd.Append( " " );
    oCmd.Append( pszWHERE );

    pszQueryStatement = oCmd.StealString();

    CPLFree( pszFields );
}

/************************************************************************/
/*                             GetFeature()                             */
/************************************************************************/

OGRFeature *OGROCITableLayer::GetFeature( GIntBig nFeatureId )

{

/* -------------------------------------------------------------------- */
/*      If we don't have an FID column scan for the desired feature.    */
/* -------------------------------------------------------------------- */
    if( pszFIDName == nullptr )
        return OGROCILayer::GetFeature( nFeatureId );

/* -------------------------------------------------------------------- */
/*      Clear any existing query.                                       */
/* -------------------------------------------------------------------- */
    ResetReading();

/* -------------------------------------------------------------------- */
/*      Build query for this specific feature.                          */
/* -------------------------------------------------------------------- */
    OGROCIStringBuf oCmd;
    char *pszFields = BuildFields();

    oCmd.Append( "SELECT " );
    oCmd.Append( pszFields );
    oCmd.Append( " FROM " );
    oCmd.Append( poFeatureDefn->GetName() );
    oCmd.Append( " " );
    oCmd.Appendf( static_cast<int>(50+strlen(pszFIDName)),
                  " WHERE \"%s\" = " CPL_FRMT_GIB " ",
                  pszFIDName, nFeatureId );

    CPLFree( pszFields );

/* -------------------------------------------------------------------- */
/*      Execute the statement.                                          */
/* -------------------------------------------------------------------- */
    if( !ExecuteQuery( oCmd.GetString() ) )
        return nullptr;

/* -------------------------------------------------------------------- */
/*      Get the feature.                                                */
/* -------------------------------------------------------------------- */
    OGRFeature *poFeature;

    poFeature = GetNextRawFeature();

    if( poFeature != nullptr && poFeature->GetGeometryRef() != nullptr )
        poFeature->GetGeometryRef()->assignSpatialReference( poSRS );

/* -------------------------------------------------------------------- */
/*      Cleanup the statement.                                          */
/* -------------------------------------------------------------------- */
    ResetReading();

/* -------------------------------------------------------------------- */
/*      verify the FID.                                                 */
/* -------------------------------------------------------------------- */
    if( poFeature != nullptr && poFeature->GetFID() != nFeatureId )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "OGROCITableLayer::GetFeature(" CPL_FRMT_GIB ") ... query returned feature " CPL_FRMT_GIB " instead!",
                  nFeatureId, poFeature->GetFID() );
        delete poFeature;
        return nullptr;
    }
    else
        return poFeature;
}

/************************************************************************/
/*                           GetNextFeature()                           */
/*                                                                      */
/*      We override the next feature method because we know that we     */
/*      implement the attribute query within the statement and so we    */
/*      don't have to test here.   Eventually the spatial query will    */
/*      be fully tested within the statement as well.                   */
/************************************************************************/

OGRFeature *OGROCITableLayer::GetNextFeature()

{

    while( true )
    {
        OGRFeature      *poFeature;

        poFeature = GetNextRawFeature();
        if( poFeature == nullptr )
        {
            CPLDebug( "OCI", "Query complete, got %d hits, and %d discards.",
                      nHits, nDiscarded );
            nHits = 0;
            nDiscarded = 0;
            return nullptr;
        }

        if( m_poFilterGeom == nullptr
            || FilterGeometry( poFeature->GetGeometryRef() ) )
        {
            nHits++;
            if( poFeature->GetGeometryRef() != nullptr )
                poFeature->GetGeometryRef()->assignSpatialReference( poSRS );
            return poFeature;
        }

        if( m_poFilterGeom != nullptr )
            nDiscarded++;

        delete poFeature;
    }
}

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

void OGROCITableLayer::ResetReading()

{
    nHits = 0;
    nDiscarded = 0;

    FlushPendingFeatures();

    BuildFullQueryStatement();

    OGROCILayer::ResetReading();
}

/************************************************************************/
/*                            BuildFields()                             */
/*                                                                      */
/*      Build list of fields to fetch, performing any required          */
/*      transformations (such as on geometry).                          */
/************************************************************************/

char *OGROCITableLayer::BuildFields()

{
    int         i;
    OGROCIStringBuf oFldList;

    if( pszGeomName )
    {
        oFldList.Append( "\"" );
        oFldList.Append( pszGeomName );
        oFldList.Append( "\"" );
        iGeomColumn = 0;
    }

    for( i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
    {
        const char *pszName = poFeatureDefn->GetFieldDefn(i)->GetNameRef();

        if( oFldList.GetLast() != '\0' )
            oFldList.Append( "," );

        oFldList.Append( "\"" );
        oFldList.Append( pszName );
        oFldList.Append( "\"" );
    }

    if( pszFIDName != nullptr )
    {
        iFIDColumn = poFeatureDefn->GetFieldCount();
        oFldList.Append( ",\"" );
        oFldList.Append( pszFIDName );
        oFldList.Append( "\"" );
    }

    return oFldList.StealString();
}

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

OGRErr OGROCITableLayer::SetAttributeFilter( const char *pszQueryIn )

{
    CPLFree(m_pszAttrQueryString);
    m_pszAttrQueryString = (pszQueryIn) ? CPLStrdup(pszQueryIn) : nullptr;

    if( (pszQueryIn == nullptr && this->pszQuery == nullptr)
        || (pszQueryIn != nullptr && this->pszQuery != nullptr
            && strcmp(pszQueryIn,this->pszQuery) == 0) )
        return OGRERR_NONE;

    CPLFree( this->pszQuery );

    if( pszQueryIn == nullptr )
        this->pszQuery = nullptr;
    else
        this->pszQuery = CPLStrdup( pszQueryIn );

    BuildWhere();

    ResetReading();

    return OGRERR_NONE;
}

/************************************************************************/
/*                             ISetFeature()                             */
/*                                                                      */
/*      We implement SetFeature() by deleting the existing row (if      */
/*      it exists), and then using CreateFeature() to write it out      */
/*      tot he table normally.  CreateFeature() will preserve the       */
/*      existing FID if possible.                                       */
/************************************************************************/

OGRErr OGROCITableLayer::ISetFeature( OGRFeature *poFeature )

{
/* -------------------------------------------------------------------- */
/*      Do some validation.                                             */
/* -------------------------------------------------------------------- */
    if( pszFIDName == nullptr )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "OGROCITableLayer::ISetFeature(" CPL_FRMT_GIB ") failed because there is "
                  "no apparent FID column on table %s.",
                  poFeature->GetFID(),
                  poFeatureDefn->GetName() );

        return OGRERR_FAILURE;
    }

    if( poFeature->GetFID() == OGRNullFID )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "OGROCITableLayer::ISetFeature(" CPL_FRMT_GIB ") failed because the feature "
                  "has no FID!", poFeature->GetFID() );

        return OGRERR_FAILURE;
    }

    OGRErr eErr = DeleteFeature(poFeature->GetFID());
    if( eErr != OGRERR_NONE )
        return eErr;

    return CreateFeature( poFeature );
}

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

OGRErr OGROCITableLayer::DeleteFeature( GIntBig nFID )

{
/* -------------------------------------------------------------------- */
/*      Do some validation.                                             */
/* -------------------------------------------------------------------- */
    if( pszFIDName == nullptr )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "OGROCITableLayer::DeleteFeature(" CPL_FRMT_GIB ") failed because there is "
                  "no apparent FID column on table %s.",
                  nFID,
                  poFeatureDefn->GetName() );

        return OGRERR_FAILURE;
    }

    if( nFID == OGRNullFID )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "OGROCITableLayer::DeleteFeature(" CPL_FRMT_GIB ") failed for Null FID",
                  nFID );

        return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Prepare the delete command, and execute.  We don't check the    */
/*      error result of the execute, since attempting to Set a          */
/*      non-existing feature may be OK.                                 */
/* -------------------------------------------------------------------- */
    OGROCIStringBuf     oCmdText;
    OGROCIStatement     oCmdStatement( poDS->GetSession() );

    oCmdText.Appendf( static_cast<int>(strlen(poFeatureDefn->GetName())+strlen(pszFIDName)+100),
                      "DELETE FROM %s WHERE \"%s\" = " CPL_FRMT_GIB,
                      poFeatureDefn->GetName(),
                      pszFIDName,
                      nFID );

    if( oCmdStatement.Execute( oCmdText.GetString() ) == CE_None )
        return (oCmdStatement.GetAffectedRows() > 0) ? OGRERR_NONE : OGRERR_NON_EXISTING_FEATURE;
    else
        return OGRERR_FAILURE;
}

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

OGRErr OGROCITableLayer::ICreateFeature( OGRFeature *poFeature )

{
/* -------------------------------------------------------------------- */
/*      Add extents of this geometry to the existing layer extents.     */
/* -------------------------------------------------------------------- */
    if( poFeature->GetGeometryRef() != nullptr )
    {
        OGREnvelope  sThisExtent;

        poFeature->GetGeometryRef()->getEnvelope( &sThisExtent );

        if( !sExtent.Contains( sThisExtent ) )
        {
            sExtent.Merge( sThisExtent );
            bExtentUpdated = true;
        }
    }

/* -------------------------------------------------------------------- */
/*      Get the first id value from open options                        */
/* -------------------------------------------------------------------- */

    this->nFirstId = -1;

    if (CSLFetchNameValue( papszOptions, "FIRST_ID" ) != nullptr)
    {
        this->nFirstId = atoi( CSLFetchNameValue( papszOptions, "FIRST_ID" ) );
    }

/* -------------------------------------------------------------------- */
/*      Get the multi load count value from open options                */
/* -------------------------------------------------------------------- */

    this->bMultiLoad = CPLFetchBool( papszOptions, "MULTI_LOAD", true );

    this->nMultiLoadCount = 100;

    if (CSLFetchNameValue( papszOptions, "MULTI_LOAD_COUNT" ) != nullptr)
    {
        this->nMultiLoadCount = atoi( CSLFetchNameValue( papszOptions,
                                                         "MULTI_LOAD_COUNT" ) );
        this->bMultiLoad = true; // overwrites MULTI_LOAD=NO
    }

/* -------------------------------------------------------------------- */
/*      Do the actual creation.                                         */
/* -------------------------------------------------------------------- */
    if( bMultiLoad )
        return BoundCreateFeature( poFeature );
    else
        return UnboundCreateFeature( poFeature );
}

/************************************************************************/
/*                        UnboundCreateFeature()                        */
/************************************************************************/

OGRErr OGROCITableLayer::UnboundCreateFeature( OGRFeature *poFeature )

{
    OGROCISession      *poSession = poDS->GetSession();
    char                *pszCommand;
    int                 bNeedComma = FALSE;
    size_t              nCommandBufSize;

/* -------------------------------------------------------------------- */
/*      Prepare SQL statement buffer.                                   */
/* -------------------------------------------------------------------- */
    nCommandBufSize = 2000;
    pszCommand = (char *) CPLMalloc(nCommandBufSize);

/* -------------------------------------------------------------------- */
/*      Form the INSERT command.                                        */
/* -------------------------------------------------------------------- */
    snprintf( pszCommand, nCommandBufSize, "INSERT INTO \"%s\"(\"", poFeatureDefn->GetName() );

    if( poFeature->GetGeometryRef() != nullptr )
    {
        bNeedComma = TRUE;
        strcat( pszCommand, pszGeomName );
    }

    if( pszFIDName != nullptr )
    {
        if( bNeedComma )
            strcat( pszCommand, "\",\"" );

        strcat( pszCommand, pszFIDName );
        bNeedComma = TRUE;
    }

    for( int i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
    {
        if( !poFeature->IsFieldSetAndNotNull( i ) )
            continue;

        if( !bNeedComma )
            bNeedComma = TRUE;
        else
            strcat( pszCommand, "\",\"" );

        snprintf( pszCommand + strlen(pszCommand),
                  nCommandBufSize - strlen(pszCommand), "%s",
                 poFeatureDefn->GetFieldDefn(i)->GetNameRef() );
    }

    strcat( pszCommand, "\") VALUES (" );

    CPLAssert( strlen(pszCommand) < nCommandBufSize );

/* -------------------------------------------------------------------- */
/*      Set the geometry                                                */
/* -------------------------------------------------------------------- */
    bNeedComma = poFeature->GetGeometryRef() != nullptr;
    if( poFeature->GetGeometryRef() != nullptr)
    {
        OGRGeometry *poGeometry = poFeature->GetGeometryRef();
        char szSDO_GEOMETRY[512];
        char szSRID[128];

        if( nSRID == -1 )
            strcpy( szSRID, "NULL" );
        else
            snprintf( szSRID, sizeof(szSRID), "%d", nSRID );

        if( wkbFlatten(poGeometry->getGeometryType()) == wkbPoint )
        {
            OGRPoint *poPoint = poGeometry->toPoint();

            if( nDimension == 2 )
                CPLsnprintf( szSDO_GEOMETRY, sizeof(szSDO_GEOMETRY),
                         "%s(%d,%s,MDSYS.SDO_POINT_TYPE(%.16g,%.16g,0),NULL,NULL)",
                         SDO_GEOMETRY, 2001, szSRID,
                         poPoint->getX(), poPoint->getY() );
            else
                CPLsnprintf( szSDO_GEOMETRY, sizeof(szSDO_GEOMETRY),
                         "%s(%d,%s,MDSYS.SDO_POINT_TYPE(%.16g,%.16g,%.16g),NULL,NULL)",
                         SDO_GEOMETRY, 3001, szSRID,
                         poPoint->getX(), poPoint->getY(), poPoint->getZ() );
        }
        else
        {
            int  nGType;

            if( TranslateToSDOGeometry( poFeature->GetGeometryRef(), &nGType )
                == OGRERR_NONE )
                CPLsnprintf( szSDO_GEOMETRY, sizeof(szSDO_GEOMETRY),
                         "%s(%d,%s,NULL,:elem_info,:ordinates)",
                         SDO_GEOMETRY, nGType, szSRID );
            else
                CPLsnprintf( szSDO_GEOMETRY, sizeof(szSDO_GEOMETRY),"NULL" );
        }

        if( strlen(pszCommand) + strlen(szSDO_GEOMETRY)
            > nCommandBufSize - 50 )
        {
            nCommandBufSize =
               strlen(pszCommand) + strlen(szSDO_GEOMETRY) + 10000;
            pszCommand = (char *) CPLRealloc(pszCommand, nCommandBufSize );
        }

        strcat( pszCommand, szSDO_GEOMETRY );
    }

/* -------------------------------------------------------------------- */
/*      Set the FID.                                                    */
/* -------------------------------------------------------------------- */
    size_t nOffset = strlen(pszCommand);

    if( pszFIDName != nullptr )
    {
        GIntBig  nFID;

        if( bNeedComma )
            strcat( pszCommand+nOffset, ", " );
        bNeedComma = TRUE;

        nOffset += strlen(pszCommand+nOffset);

        nFID = poFeature->GetFID();
        if( nFID == OGRNullFID )
        {
            if( iNextFIDToWrite < 0 )
            {
                iNextFIDToWrite = GetMaxFID() + 1;
            }
            nFID = iNextFIDToWrite++;
            poFeature->SetFID( nFID );
        }
        snprintf( pszCommand+nOffset, nCommandBufSize - nOffset, CPL_FRMT_GIB, nFID );
    }

/* -------------------------------------------------------------------- */
/*      Set the other fields.                                           */
/* -------------------------------------------------------------------- */
    for( int i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
    {
        if( !poFeature->IsFieldSetAndNotNull( i ) )
            continue;

        OGRFieldDefn *poFldDefn = poFeatureDefn->GetFieldDefn(i);
        const char *pszStrValue = poFeature->GetFieldAsString(i);

        if( bNeedComma )
            strcat( pszCommand+nOffset, ", " );
        else
            bNeedComma = TRUE;

        if( strlen(pszStrValue) + strlen(pszCommand+nOffset) + nOffset
            > nCommandBufSize-50 )
        {
            nCommandBufSize = strlen(pszCommand) + strlen(pszStrValue) + 10000;
            pszCommand = (char *) CPLRealloc(pszCommand, nCommandBufSize );
        }

        if( poFldDefn->GetType() == OFTInteger
            || poFldDefn->GetType() == OFTInteger64
            || poFldDefn->GetType() == OFTReal )
        {
            if( poFldDefn->GetWidth() > 0 && bPreservePrecision
                && (int) strlen(pszStrValue) > poFldDefn->GetWidth() )
            {
                strcat( pszCommand+nOffset, "NULL" );
                ReportTruncation( poFldDefn );
            }
            else
                strcat( pszCommand+nOffset, pszStrValue );
        }
        else
        {
            int         iChar;

            /* We need to quote and escape string fields. */
            strcat( pszCommand+nOffset, "'" );

            nOffset += strlen(pszCommand+nOffset);

            for( iChar = 0; pszStrValue[iChar] != '\0'; iChar++ )
            {
                if( poFldDefn->GetWidth() != 0 && bPreservePrecision
                    && iChar >= poFldDefn->GetWidth() )
                {
                    ReportTruncation( poFldDefn );
                    break;
                }

                if( pszStrValue[iChar] == '\'' )
                {
                    pszCommand[nOffset++] = '\'';
                    pszCommand[nOffset++] = pszStrValue[iChar];
                }
                else
                    pszCommand[nOffset++] = pszStrValue[iChar];
            }
            pszCommand[nOffset] = '\0';

            strcat( pszCommand+nOffset, "'" );
        }
        nOffset += strlen(pszCommand+nOffset);
    }

    strcat( pszCommand+nOffset, ")" );

/* -------------------------------------------------------------------- */
/*      Prepare statement.                                              */
/* -------------------------------------------------------------------- */
    OGROCIStatement oInsert( poSession );
    int  bHaveOrdinates = strstr(pszCommand,":ordinates") != nullptr;
    int  bHaveElemInfo = strstr(pszCommand,":elem_info") != nullptr;

    if( oInsert.Prepare( pszCommand ) != CE_None )
    {
        CPLFree( pszCommand );
        return OGRERR_FAILURE;
    }

    CPLFree( pszCommand );

/* -------------------------------------------------------------------- */
/*      Bind and translate the elem_info if we have some.               */
/* -------------------------------------------------------------------- */
    if( bHaveElemInfo )
    {
        OCIBind *hBindOrd = nullptr;
        int i;
        OCINumber oci_number;

        // Create or clear VARRAY
        if( hElemInfoVARRAY == nullptr )
        {
            if( poSession->Failed(
                OCIObjectNew( poSession->hEnv, poSession->hError,
                              poSession->hSvcCtx, OCI_TYPECODE_VARRAY,
                              poSession->hElemInfoTDO, (dvoid *)nullptr,
                              OCI_DURATION_SESSION,
                              FALSE, (dvoid **)&hElemInfoVARRAY),
                "OCIObjectNew(hElemInfoVARRAY)") )
                return OGRERR_FAILURE;
        }
        else
        {
            sb4  nOldCount;

            OCICollSize( poSession->hEnv, poSession->hError,
                         hElemInfoVARRAY, &nOldCount );
            OCICollTrim( poSession->hEnv, poSession->hError,
                         nOldCount, hElemInfoVARRAY );
        }

        // Prepare the VARRAY of ordinate values.
        for (i = 0; i < nElemInfoCount; i++)
        {
            if( poSession->Failed(
                OCINumberFromInt( poSession->hError,
                                  (dvoid *) (panElemInfo + i),
                                  (uword)sizeof(int),
                                  OCI_NUMBER_SIGNED,
                                  &oci_number),
                "OCINumberFromInt") )
                return OGRERR_FAILURE;

            if( poSession->Failed(
                OCICollAppend( poSession->hEnv, poSession->hError,
                               (dvoid *) &oci_number,
                               (dvoid *)nullptr, hElemInfoVARRAY),
                "OCICollAppend") )
                return OGRERR_FAILURE;
        }

        // Do the binding.
        if( poSession->Failed(
            OCIBindByName( oInsert.GetStatement(), &hBindOrd,
                           poSession->hError,
                           (text *) ":elem_info", (sb4) -1, (dvoid *) nullptr,
                           (sb4) 0, SQLT_NTY, (dvoid *)nullptr, (ub2 *)nullptr,
                           (ub2 *)nullptr, (ub4)0, (ub4 *)nullptr,
                           (ub4)OCI_DEFAULT),
            "OCIBindByName(:elem_info)") )
            return OGRERR_FAILURE;

        if( poSession->Failed(
            OCIBindObject( hBindOrd, poSession->hError,
                           poSession->hElemInfoTDO,
                           (dvoid **)&hElemInfoVARRAY, (ub4 *)nullptr,
                           (dvoid **)nullptr, (ub4 *)nullptr),
            "OCIBindObject(:elem_info)" ) )
            return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Bind and translate the ordinates if we have some.               */
/* -------------------------------------------------------------------- */
    if( bHaveOrdinates )
    {
        OCIBind *hBindOrd = nullptr;
        int i;
        OCINumber oci_number;

        // Create or clear VARRAY
        if( hOrdVARRAY == nullptr )
        {
            if( poSession->Failed(
                OCIObjectNew( poSession->hEnv, poSession->hError,
                              poSession->hSvcCtx, OCI_TYPECODE_VARRAY,
                              poSession->hOrdinatesTDO, (dvoid *)nullptr,
                              OCI_DURATION_SESSION,
                              FALSE, (dvoid **)&hOrdVARRAY),
                "OCIObjectNew(hOrdVARRAY)") )
                return OGRERR_FAILURE;
        }
        else
        {
            sb4  nOldCount;

            OCICollSize( poSession->hEnv, poSession->hError,
                         hOrdVARRAY, &nOldCount );
            OCICollTrim( poSession->hEnv, poSession->hError,
                         nOldCount, hOrdVARRAY );
        }

        // Prepare the VARRAY of ordinate values.
        for (i = 0; i < nOrdinalCount; i++)
        {
            if( poSession->Failed(
                OCINumberFromReal( poSession->hError,
                                   (dvoid *) (padfOrdinals + i),
                                   (uword)sizeof(double),
                                   &oci_number),
                "OCINumberFromReal") )
                return OGRERR_FAILURE;

            if( poSession->Failed(
                OCICollAppend( poSession->hEnv, poSession->hError,
                               (dvoid *) &oci_number,
                               (dvoid *)nullptr, hOrdVARRAY),
                "OCICollAppend") )
                return OGRERR_FAILURE;
        }

        // Do the binding.
        if( poSession->Failed(
            OCIBindByName( oInsert.GetStatement(), &hBindOrd,
                           poSession->hError,
                           (text *) ":ordinates", (sb4) -1, (dvoid *) nullptr,
                           (sb4) 0, SQLT_NTY, (dvoid *)nullptr, (ub2 *)nullptr,
                           (ub2 *)nullptr, (ub4)0, (ub4 *)nullptr,
                           (ub4)OCI_DEFAULT),
            "OCIBindByName(:ordinates)") )
            return OGRERR_FAILURE;

        if( poSession->Failed(
            OCIBindObject( hBindOrd, poSession->hError,
                           poSession->hOrdinatesTDO,
                           (dvoid **)&hOrdVARRAY, (ub4 *)nullptr,
                           (dvoid **)nullptr, (ub4 *)nullptr),
            "OCIBindObject(:ordinates)" ) )
            return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Execute the insert.                                             */
/* -------------------------------------------------------------------- */
    if( oInsert.Execute( nullptr ) != CE_None )
        return OGRERR_FAILURE;
    else
        return OGRERR_NONE;
}

/************************************************************************/
/*                           GetExtent()                                */
/************************************************************************/

OGRErr OGROCITableLayer::GetExtent(OGREnvelope *psExtent, int bForce)

{
    CPLAssert( nullptr != psExtent );

    OGRErr err = OGRERR_FAILURE;

    if( EQUAL(GetGeometryColumn(),"") )
    {
        return OGRERR_NONE;
    }

/* -------------------------------------------------------------------- */
/*      Build query command.                                        */
/* -------------------------------------------------------------------- */
    CPLAssert( nullptr != pszGeomName );

    OGROCIStringBuf oCommand;
    oCommand.Appendf( 1000, "SELECT "
                      "MIN(SDO_GEOM.SDO_MIN_MBR_ORDINATE(t.%s,m.DIMINFO,1)) AS MINX,"
                      "MIN(SDO_GEOM.SDO_MIN_MBR_ORDINATE(t.%s,m.DIMINFO,2)) AS MINY,"
                      "MAX(SDO_GEOM.SDO_MAX_MBR_ORDINATE(t.%s,m.DIMINFO,1)) AS MAXX,"
                      "MAX(SDO_GEOM.SDO_MAX_MBR_ORDINATE(t.%s,m.DIMINFO,2)) AS MAXY "
                      "FROM ALL_SDO_GEOM_METADATA m, ",
                      pszGeomName, pszGeomName, pszGeomName, pszGeomName );

    if( osOwner != "" )
    {
        oCommand.Appendf( 500, " %s.%s t ",
                          osOwner.c_str(), osTableName.c_str() );
    }
    else
    {
        oCommand.Appendf( 500, " %s t ",
                          osTableName.c_str() );
    }

    oCommand.Appendf( 500, "WHERE m.TABLE_NAME = UPPER('%s') AND m.COLUMN_NAME = UPPER('%s')",
                      osTableName.c_str(), pszGeomName );

    if( osOwner != "" )
    {
        oCommand.Appendf( 500, " AND OWNER = UPPER('%s')", osOwner.c_str() );
    }

/* -------------------------------------------------------------------- */
/*      Execute query command.                                          */
/* -------------------------------------------------------------------- */
    OGROCISession *poSession = poDS->GetSession();
    CPLAssert( nullptr != poSession );

    OGROCIStatement oGetExtent( poSession );

    if( oGetExtent.Execute( oCommand.GetString() ) == CE_None )
    {
        char **papszRow = oGetExtent.SimpleFetchRow();

        if( papszRow != nullptr
            && papszRow[0] != nullptr && papszRow[1] != nullptr
            && papszRow[2] != nullptr && papszRow[3] != nullptr )
        {
            psExtent->MinX = CPLAtof(papszRow[0]);
            psExtent->MinY = CPLAtof(papszRow[1]);
            psExtent->MaxX = CPLAtof(papszRow[2]);
            psExtent->MaxY = CPLAtof(papszRow[3]);

            err = OGRERR_NONE;
        }
    }

/* -------------------------------------------------------------------- */
/*      Query spatial extent of layer using default,                    */
/*      but not optimized implementation.                               */
/* -------------------------------------------------------------------- */
    if( err != OGRERR_NONE )
    {
        err = OGRLayer::GetExtent( psExtent, bForce );
        CPLDebug( "OCI",
                  "Failing to query extent of %s using default GetExtent",
                  osTableName.c_str() );
    }

    return err;
}

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

int OGROCITableLayer::TestCapability( const char * pszCap )

{
    if( EQUAL(pszCap,OLCSequentialWrite)
             || EQUAL(pszCap,OLCRandomWrite) )
        return bUpdateAccess;

    else if( EQUAL(pszCap,OLCCreateField) )
        return bUpdateAccess;

    else
        return OGROCILayer::TestCapability( pszCap );
}

/************************************************************************/
/*                          GetFeatureCount()                           */
/*                                                                      */
/*      If a spatial filter is in effect, we turn control over to       */
/*      the generic counter.  Otherwise we return the total count.      */
/*      Eventually we should consider implementing a more efficient     */
/*      way of counting features matching a spatial query.              */
/************************************************************************/

GIntBig OGROCITableLayer::GetFeatureCount( int bForce )

{
/* -------------------------------------------------------------------- */
/*      Use a more brute force mechanism if we have a spatial query     */
/*      in play.                                                        */
/* -------------------------------------------------------------------- */
    if( m_poFilterGeom != nullptr )
        return OGROCILayer::GetFeatureCount( bForce );

/* -------------------------------------------------------------------- */
/*      In theory it might be wise to cache this result, but it         */
/*      won't be trivial to work out the lifetime of the value.         */
/*      After all someone else could be adding records from another     */
/*      application when working against a database.                    */
/* -------------------------------------------------------------------- */
    OGROCISession      *poSession = poDS->GetSession();
    OGROCIStatement    oGetCount( poSession );
    char               szCommand[1024];
    char               **papszResult;

    snprintf( szCommand, sizeof(szCommand), "SELECT COUNT(*) FROM %s %s",
             poFeatureDefn->GetName(), pszWHERE );

    oGetCount.Execute( szCommand );

    papszResult = oGetCount.SimpleFetchRow();

    if( CSLCount(papszResult) < 1 )
    {
        CPLDebug( "OCI", "Fast get count failed, doing hard way." );
        return OGROCILayer::GetFeatureCount( bForce );
    }

    return CPLAtoGIntBig(papszResult[0]);
}

/************************************************************************/
/*                         UpdateLayerExtents()                         */
/************************************************************************/

void OGROCITableLayer::UpdateLayerExtents()

{
    if( !bExtentUpdated )
        return;

    bExtentUpdated = false;

/* -------------------------------------------------------------------- */
/*      Do we have existing layer extents we need to merge in to the    */
/*      ones we collected as we created features?                       */
/* -------------------------------------------------------------------- */
    bool bHaveOldExtent = false;

    if( !bNewLayer && pszGeomName )
    {
        OGROCIStringBuf oCommand;

        oCommand.Appendf(1000,
                          "select min(case when r=1 then sdo_lb else null end) minx, min(case when r=2 then sdo_lb else null end) miny, "
                          "min(case when r=1 then sdo_ub else null end) maxx, min(case when r=2 then sdo_ub else null end) maxy"
                          " from (SELECT d.sdo_dimname, d.sdo_lb, sdo_ub, sdo_tolerance, rownum r"
                          " FROM ALL_SDO_GEOM_METADATA m, table(m.diminfo) d"
                          " where m.table_name = UPPER('%s') and m.COLUMN_NAME = UPPER('%s')",
                          osTableName.c_str(), pszGeomName );

        if( osOwner != "" )
        {
            oCommand.Appendf(500, " AND OWNER = UPPER('%s')", osOwner.c_str() );
        }

        oCommand.Append(" ) ");

        OGROCISession *poSession = poDS->GetSession();
        CPLAssert( nullptr != poSession );

        OGROCIStatement oGetExtent( poSession );

        if( oGetExtent.Execute( oCommand.GetString() ) == CE_None )
        {
            char **papszRow = oGetExtent.SimpleFetchRow();

            if( papszRow != nullptr
                && papszRow[0] != nullptr && papszRow[1] != nullptr
                && papszRow[2] != nullptr && papszRow[3] != nullptr )
            {
                OGREnvelope sOldExtent;

                bHaveOldExtent = true;

                sOldExtent.MinX = CPLAtof(papszRow[0]);
                sOldExtent.MinY = CPLAtof(papszRow[1]);
                sOldExtent.MaxX = CPLAtof(papszRow[2]);
                sOldExtent.MaxY = CPLAtof(papszRow[3]);

                if( sOldExtent.Contains( sExtent ) )
                {
                    // nothing to do!
                    sExtent = sOldExtent;
                    bExtentUpdated = false;
                    return;
                }
                else
                {
                    sExtent.Merge( sOldExtent );
                }
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Establish the extents and resolution to use.                    */
/* -------------------------------------------------------------------- */
    double           dfResSize;
    double           dfXMin, dfXMax, dfXRes;
    double           dfYMin, dfYMax, dfYRes;
    double           dfZMin, dfZMax, dfZRes;

    if( sExtent.MaxX - sExtent.MinX > 400 )
        dfResSize = 0.001;
    else
        dfResSize = 0.0000001;

    dfXMin = sExtent.MinX - dfResSize * 3;
    dfXMax = sExtent.MaxX + dfResSize * 3;
    dfXRes = dfResSize;
    ParseDIMINFO( "DIMINFO_X", &dfXMin, &dfXMax, &dfXRes );

    dfYMin = sExtent.MinY - dfResSize * 3;
    dfYMax = sExtent.MaxY + dfResSize * 3;
    dfYRes = dfResSize;
    ParseDIMINFO( "DIMINFO_Y", &dfYMin, &dfYMax, &dfYRes );

    dfZMin = -100000.0;
    dfZMax = 100000.0;
    dfZRes = 0.002;
    ParseDIMINFO( "DIMINFO_Z", &dfZMin, &dfZMax, &dfZRes );

/* -------------------------------------------------------------------- */
/*      If we already have an extent in the table, we will need to      */
/*      update it in place.                                             */
/* -------------------------------------------------------------------- */
    OGROCIStringBuf  sDimUpdate;

    if( bHaveOldExtent )
    {
        sDimUpdate.Append( "UPDATE USER_SDO_GEOM_METADATA " );
        sDimUpdate.Append( "SET DIMINFO =" );
        sDimUpdate.Append( "MDSYS.SDO_DIM_ARRAY(" );
        sDimUpdate.Appendf(200,
                           "MDSYS.SDO_DIM_ELEMENT('X',%.16g,%.16g,%.12g)",
                           dfXMin, dfXMax, dfXRes );
        sDimUpdate.Appendf(200,
                           ",MDSYS.SDO_DIM_ELEMENT('Y',%.16g,%.16g,%.12g)",
                           dfYMin, dfYMax, dfYRes );

        if( nDimension == 3 )
        {
            sDimUpdate.Appendf(200,
                               ",MDSYS.SDO_DIM_ELEMENT('Z',%.16g,%.16g,%.12g)",
                               dfZMin, dfZMax, dfZRes );
        }

        sDimUpdate.Appendf(static_cast<int>(strlen(poFeatureDefn->GetName()) + 100),
                           ") WHERE TABLE_NAME = '%s'", poFeatureDefn->GetName());
    }
    else
    {
/* -------------------------------------------------------------------- */
/*      Prepare dimension update statement.                             */
/* -------------------------------------------------------------------- */
        sDimUpdate.Append( "INSERT INTO USER_SDO_GEOM_METADATA VALUES " );
        sDimUpdate.Appendf( static_cast<int>(strlen(poFeatureDefn->GetName()) + 100),
                            "('%s', '%s', ",
                            poFeatureDefn->GetName(),
                            pszGeomName );

        sDimUpdate.Append( "MDSYS.SDO_DIM_ARRAY(" );
        sDimUpdate.Appendf(200,
                           "MDSYS.SDO_DIM_ELEMENT('X',%.16g,%.16g,%.12g)",
                           dfXMin, dfXMax, dfXRes );
        sDimUpdate.Appendf(200,
                           ",MDSYS.SDO_DIM_ELEMENT('Y',%.16g,%.16g,%.12g)",
                           dfYMin, dfYMax, dfYRes );

        if( nDimension == 3 )
        {
            sDimUpdate.Appendf(200,
                               ",MDSYS.SDO_DIM_ELEMENT('Z',%.16g,%.16g,%.12g)",
                               dfZMin, dfZMax, dfZRes );
        }

        if( nSRID == -1 )
            sDimUpdate.Append( "), NULL)" );
        else
            sDimUpdate.Appendf( 100, "), %d)", nSRID );
    }

/* -------------------------------------------------------------------- */
/*      Run the update/insert command.                                  */
/* -------------------------------------------------------------------- */
    OGROCIStatement oExecStatement( poDS->GetSession() );

    oExecStatement.Execute( sDimUpdate.GetString() );
}

/************************************************************************/
/*                   AllocAndBindForWrite()                             */
/************************************************************************/

int OGROCITableLayer::AllocAndBindForWrite()

{
    OGROCISession      *poSession = poDS->GetSession();
    int i;

    CPLAssert( nWriteCacheMax == 0 );

/* -------------------------------------------------------------------- */
/*      Decide on the number of rows we want to be able to cache at     */
/*      a time.                                                         */
/* -------------------------------------------------------------------- */
    nWriteCacheMax = nMultiLoadCount;

/* -------------------------------------------------------------------- */
/*      Collect the INSERT statement.                                   */
/* -------------------------------------------------------------------- */
    OGROCIStringBuf oCmdBuf;

    oCmdBuf.Append( "INSERT /*+ APPEND */ INTO \"" );
    oCmdBuf.Append( poFeatureDefn->GetName() );
    oCmdBuf.Append( "\"(\"" );
    oCmdBuf.Append( pszFIDName );

    if (GetGeomType() != wkbNone)
    {
       oCmdBuf.Append( "\",\"" );
       oCmdBuf.Append( pszGeomName );
    }

    for( i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
    {
        oCmdBuf.Append( "\",\"" );
        oCmdBuf.Append( poFeatureDefn->GetFieldDefn(i)->GetNameRef() );
    }

    oCmdBuf.Append( "\") VALUES ( :fid " );

    if (GetGeomType() != wkbNone)
        oCmdBuf.Append( ", :geometry" );

    for( i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
    {
        oCmdBuf.Append( ", " );
        oCmdBuf.Appendf( 20, " :field_%d", i );
    }

    oCmdBuf.Append( ") " );

/* -------------------------------------------------------------------- */
/*      Bind and Prepare it.                                            */
/* -------------------------------------------------------------------- */
    poBoundStatement = new OGROCIStatement( poSession );
    poBoundStatement->Prepare( oCmdBuf.GetString() );

/* -------------------------------------------------------------------- */
/*      Setup geometry indicator information.                           */
/* -------------------------------------------------------------------- */
    if (GetGeomType() != wkbNone)
    {
        pasWriteGeomInd = (SDO_GEOMETRY_ind *)
            CPLCalloc(sizeof(SDO_GEOMETRY_ind),nWriteCacheMax);

        papsWriteGeomIndMap = (SDO_GEOMETRY_ind **)
            CPLCalloc(sizeof(SDO_GEOMETRY_ind *),nWriteCacheMax);

        for( i = 0; i < nWriteCacheMax; i++ )
            papsWriteGeomIndMap[i] = pasWriteGeomInd + i;

/* -------------------------------------------------------------------- */
/*      Setup all the required geometry objects, and the                */
/*      corresponding indicator map.                                    */
/* -------------------------------------------------------------------- */
        pasWriteGeoms = (SDO_GEOMETRY_TYPE *)
            CPLCalloc( sizeof(SDO_GEOMETRY_TYPE), nWriteCacheMax);
        papsWriteGeomMap = (SDO_GEOMETRY_TYPE **)
            CPLCalloc( sizeof(SDO_GEOMETRY_TYPE *), nWriteCacheMax );

        for( i = 0; i < nWriteCacheMax; i++ )
            papsWriteGeomMap[i] = pasWriteGeoms + i;

/* -------------------------------------------------------------------- */
/*      Allocate VARRAYs for the elem_info and ordinates.               */
/* -------------------------------------------------------------------- */
        for( i = 0; i < nWriteCacheMax; i++ )
        {
            if( poSession->Failed(
                OCIObjectNew( poSession->hEnv, poSession->hError,
                              poSession->hSvcCtx, OCI_TYPECODE_VARRAY,
                              poSession->hElemInfoTDO, (dvoid *)nullptr,
                              OCI_DURATION_SESSION,
                              FALSE,
                              (dvoid **) &(pasWriteGeoms[i].sdo_elem_info)),
                "OCIObjectNew(elem_info)") )
                return FALSE;

            if( poSession->Failed(
                OCIObjectNew( poSession->hEnv, poSession->hError,
                              poSession->hSvcCtx, OCI_TYPECODE_VARRAY,
                              poSession->hOrdinatesTDO, (dvoid *)nullptr,
                              OCI_DURATION_SESSION,
                              FALSE,
                              (dvoid **) &(pasWriteGeoms[i].sdo_ordinates)),
                "OCIObjectNew(ordinates)") )
                return FALSE;
        }

/* -------------------------------------------------------------------- */
/*      Bind the geometry column.                                       */
/* -------------------------------------------------------------------- */
        if( poBoundStatement->BindObject(
            ":geometry", papsWriteGeomMap, poSession->hGeometryTDO,
            (void**) papsWriteGeomIndMap) != CE_None )
            return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Bind the FID column.                                            */
/* -------------------------------------------------------------------- */
    panWriteFIDs = (int *) CPLMalloc(sizeof(int) * nWriteCacheMax );

    if( poBoundStatement->BindScalar( ":fid", panWriteFIDs, sizeof(int),
                                      SQLT_INT ) != CE_None )
        return FALSE;

/* -------------------------------------------------------------------- */
/*      Allocate each of the column data bind arrays.                   */
/* -------------------------------------------------------------------- */

    papWriteFields = (void **)
        CPLMalloc(sizeof(void*) * poFeatureDefn->GetFieldCount() );
    papaeWriteFieldInd = (OCIInd **)
        CPLCalloc(sizeof(OCIInd*),poFeatureDefn->GetFieldCount() );

    for( i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
    {
        OGRFieldDefn *poFldDefn = poFeatureDefn->GetFieldDefn(i);
        char szFieldPlaceholderName[80];

        snprintf( szFieldPlaceholderName, sizeof(szFieldPlaceholderName), ":field_%d", i );

        papaeWriteFieldInd[i] = (OCIInd *)
            CPLCalloc(sizeof(OCIInd), nWriteCacheMax );

        if( poFldDefn->GetType() == OFTInteger )
        {
            papWriteFields[i] =
                (void *) CPLCalloc( sizeof(int), nWriteCacheMax );

            if( poBoundStatement->BindScalar(
                    szFieldPlaceholderName, papWriteFields[i],
                    sizeof(int), SQLT_INT, papaeWriteFieldInd[i] ) != CE_None )
                return FALSE;
        }
        else if( poFldDefn->GetType() == OFTInteger64 )
        {
            papWriteFields[i] =
                (void *) CPLCalloc( sizeof(GIntBig), nWriteCacheMax );

            if( poBoundStatement->BindScalar(
                    szFieldPlaceholderName, papWriteFields[i],
                    sizeof(GIntBig), SQLT_INT, papaeWriteFieldInd[i] ) != CE_None )
                return FALSE;
        }
        else if( poFldDefn->GetType() == OFTReal )
        {
            papWriteFields[i] = (void *) CPLCalloc( sizeof(double),
                                                    nWriteCacheMax );

            if( poBoundStatement->BindScalar(
                    szFieldPlaceholderName, papWriteFields[i],
                    sizeof(double), SQLT_FLT, papaeWriteFieldInd[i] ) != CE_None )
                return FALSE;
        }
        else
        {
            int nEachBufSize = nDefaultStringSize + 1;

            if( poFldDefn->GetType() == OFTString
                && poFldDefn->GetWidth() != 0 )
                nEachBufSize = poFldDefn->GetWidth() + 1;

            papWriteFields[i] =
                (void *) CPLCalloc( nEachBufSize, nWriteCacheMax );

            if( poBoundStatement->BindScalar(
                    szFieldPlaceholderName, papWriteFields[i],
                    nEachBufSize, SQLT_STR, papaeWriteFieldInd[i]) != CE_None )
                return FALSE;
        }
    }

    return TRUE;
}

/************************************************************************/
/*                         BoundCreateFeature()                         */
/************************************************************************/

OGRErr OGROCITableLayer::BoundCreateFeature( OGRFeature *poFeature )

{
    OGROCISession      *poSession = poDS->GetSession();
    int                iCache, i;
    OGRErr             eErr;
    OCINumber          oci_number;

    /* If an unset field has a default value, the current implementation */
    /* of BoundCreateFeature() doesn't work. */
    for( i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
    {
        if( !poFeature->IsFieldSetAndNotNull( i ) &&
            poFeature->GetFieldDefnRef(i)->GetDefault() != nullptr )
        {
            FlushPendingFeatures();
            return UnboundCreateFeature(poFeature);
        }
    }

    if( !poFeature->Validate( OGR_F_VAL_NULL | OGR_F_VAL_ALLOW_NULL_WHEN_DEFAULT, TRUE ) )
        return OGRERR_FAILURE;

    iCache = nWriteCacheUsed;

/* -------------------------------------------------------------------- */
/*  Initiate the Insert                                                 */
/* -------------------------------------------------------------------- */
    if( nWriteCacheMax == 0 )
    {
        if( !AllocAndBindForWrite() )
            return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Set the geometry                                                */
/* -------------------------------------------------------------------- */
    if( poFeature->GetGeometryRef() != nullptr )
    {
        SDO_GEOMETRY_TYPE *psGeom = pasWriteGeoms + iCache;
        SDO_GEOMETRY_ind  *psInd  = pasWriteGeomInd + iCache;
        OGRGeometry *poGeometry = poFeature->GetGeometryRef();
        int nGType;

        psInd->_atomic = OCI_IND_NOTNULL;

        if( nSRID == -1 )
            psInd->sdo_srid = OCI_IND_NULL;
        else
        {
            psInd->sdo_srid = OCI_IND_NOTNULL;
            OCINumberFromInt( poSession->hError, &nSRID,
                              (uword)sizeof(int), OCI_NUMBER_SIGNED,
                              &(psGeom->sdo_srid) );
        }

        /* special more efficient case for simple points */
        if( wkbFlatten(poGeometry->getGeometryType()) == wkbPoint )
        {
            OGRPoint *poPoint = poGeometry->toPoint();
            double dfValue;

            psInd->sdo_point._atomic = OCI_IND_NOTNULL;
            psInd->sdo_elem_info = OCI_IND_NULL;
            psInd->sdo_ordinates = OCI_IND_NULL;

            dfValue = poPoint->getX();
            OCINumberFromReal( poSession->hError, &dfValue,
                               (uword)sizeof(double),
                               &(psGeom->sdo_point.x) );

            dfValue = poPoint->getY();
            OCINumberFromReal( poSession->hError, &dfValue,
                               (uword)sizeof(double),
                               &(psGeom->sdo_point.y) );

            if( nDimension == 2 )
            {
                nGType = 2001;
                psInd->sdo_point.z = OCI_IND_NULL;
            }
            else
            {
                nGType = 3001;
                psInd->sdo_point.z = OCI_IND_NOTNULL;

                dfValue = poPoint->getZ();
                OCINumberFromReal( poSession->hError, &dfValue,
                                   (uword)sizeof(double),
                                   &(psGeom->sdo_point.z) );
            }
        }
        else
        {
            psInd->sdo_point._atomic = OCI_IND_NULL;
            psInd->sdo_elem_info = OCI_IND_NOTNULL;
            psInd->sdo_ordinates = OCI_IND_NOTNULL;

            eErr = TranslateToSDOGeometry( poFeature->GetGeometryRef(),
                                           &nGType );

            if( eErr != OGRERR_NONE )
                return eErr;

            /* Clear the existing eleminfo and ordinates arrays */
            sb4  nOldCount;

            OCICollSize( poSession->hEnv, poSession->hError,
                         psGeom->sdo_elem_info, &nOldCount );
            OCICollTrim( poSession->hEnv, poSession->hError,
                         nOldCount, psGeom->sdo_elem_info );

            OCICollSize( poSession->hEnv, poSession->hError,
                         psGeom->sdo_ordinates, &nOldCount );
            OCICollTrim( poSession->hEnv, poSession->hError,
                         nOldCount, psGeom->sdo_ordinates );

            // Prepare the VARRAY of element values.
            for (i = 0; i < nElemInfoCount; i++)
            {
                OCINumberFromInt( poSession->hError,
                                  (dvoid *) (panElemInfo + i),
                                  (uword)sizeof(int), OCI_NUMBER_SIGNED,
                                  &oci_number );

                OCICollAppend( poSession->hEnv, poSession->hError,
                               (dvoid *) &oci_number,
                               (dvoid *)nullptr, psGeom->sdo_elem_info );
            }

            // Prepare the VARRAY of ordinate values.
            for (i = 0; i < nOrdinalCount; i++)
            {
                OCINumberFromReal( poSession->hError,
                                   (dvoid *) (padfOrdinals + i),
                                   (uword)sizeof(double), &oci_number );
                OCICollAppend( poSession->hEnv, poSession->hError,
                               (dvoid *) &oci_number,
                               (dvoid *)nullptr, psGeom->sdo_ordinates );
            }
        }

        psInd->sdo_gtype = OCI_IND_NOTNULL;
        OCINumberFromInt( poSession->hError, &nGType,
                          (uword)sizeof(int), OCI_NUMBER_SIGNED,
                          &(psGeom->sdo_gtype) );
    }
    else if( pasWriteGeomInd != nullptr )
    {
        SDO_GEOMETRY_ind  *psInd  = pasWriteGeomInd + iCache;
        psInd->_atomic = OCI_IND_NULL;
        psInd->sdo_srid = OCI_IND_NULL;
        psInd->sdo_point._atomic = OCI_IND_NULL;
        psInd->sdo_elem_info = OCI_IND_NULL;
        psInd->sdo_ordinates = OCI_IND_NULL;
        psInd->sdo_gtype = OCI_IND_NULL;
    }

/* -------------------------------------------------------------------- */
/*      Set the FID.                                                    */
/* -------------------------------------------------------------------- */
    if( poFeature->GetFID() == OGRNullFID )
    {
        if( iNextFIDToWrite < 0 )
        {
            iNextFIDToWrite = GetMaxFID() + 1;
        }

        poFeature->SetFID( iNextFIDToWrite++ );
    }

    panWriteFIDs[iCache] = static_cast<int>(poFeature->GetFID());

/* -------------------------------------------------------------------- */
/*      Set the other fields.                                           */
/* -------------------------------------------------------------------- */
    for( i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
    {
        if( !poFeature->IsFieldSetAndNotNull( i ) )
        {
            papaeWriteFieldInd[i][iCache] = OCI_IND_NULL;
            continue;
        }

        papaeWriteFieldInd[i][iCache] = OCI_IND_NOTNULL;

        OGRFieldDefn *poFldDefn = poFeatureDefn->GetFieldDefn(i);

        if( poFldDefn->GetType() == OFTInteger )
            ((int *) (papWriteFields[i]))[iCache] =
                poFeature->GetFieldAsInteger( i );

        else if( poFldDefn->GetType() == OFTInteger64 )
            ((GIntBig *) (papWriteFields[i]))[iCache] =
                poFeature->GetFieldAsInteger64( i );

        else if( poFldDefn->GetType() == OFTReal )
            ((double *) (papWriteFields[i]))[iCache] =
                poFeature->GetFieldAsDouble( i );

        else
        {
            int nLen = 1;
            int nEachBufSize = nDefaultStringSize + 1;
            const char *pszStrValue = poFeature->GetFieldAsString(i);

            if( poFldDefn->GetType() == OFTString
                && poFldDefn->GetWidth() != 0 )
                nEachBufSize = poFldDefn->GetWidth() + 1;

            nLen = static_cast<int>(strlen(pszStrValue));
            if( nLen > nEachBufSize-1 )
                nLen = nEachBufSize-1;

            char *pszTarget = ((char*)papWriteFields[i]) + iCache*nEachBufSize;
            strncpy( pszTarget, pszStrValue, nLen );
            pszTarget[nLen] = '\0';
        }
    }

/* -------------------------------------------------------------------- */
/*      Do we need to flush out a full set of rows?                     */
/* -------------------------------------------------------------------- */
    nWriteCacheUsed++;

    if( nWriteCacheUsed == nWriteCacheMax )
        return FlushPendingFeatures();
    else
        return OGRERR_NONE;
}

/************************************************************************/
/*                        FlushPendingFeatures()                        */
/************************************************************************/

OGRErr OGROCITableLayer::FlushPendingFeatures()

{
    OGROCISession      *poSession = poDS->GetSession();

    if( nWriteCacheUsed > 0 )
    {
        CPLDebug( "OCI", "Flushing %d features on layer %s",
                  nWriteCacheUsed, poFeatureDefn->GetName() );

        if( poSession->Failed(
                OCIStmtExecute( poSession->hSvcCtx,
                                poBoundStatement->GetStatement(),
                                poSession->hError, (ub4) nWriteCacheUsed,
                                (ub4) 0,
                                (OCISnapshot *)nullptr, (OCISnapshot *)nullptr,
                                (ub4) OCI_COMMIT_ON_SUCCESS ),
                "OCIStmtExecute" ) )
        {
            nWriteCacheUsed = 0;
            return OGRERR_FAILURE;
        }
        else
        {
            nWriteCacheUsed = 0;
            return OGRERR_NONE;
        }
    }
    else
        return OGRERR_NONE;
}

/************************************************************************/
/*                             SyncToDisk()                             */
/*                                                                      */
/*      Perhaps we should also be putting the metadata into a           */
/*      usable state?                                                   */
/************************************************************************/

OGRErr OGROCITableLayer::SyncToDisk()

{
    OGRErr eErr = FlushPendingFeatures();

    UpdateLayerExtents();

    CreateSpatialIndex();

    bNewLayer = FALSE;

    return eErr;
}

/*************************************************************************/
/*                         CreateSpatialIndex()                          */
/*************************************************************************/

void OGROCITableLayer::CreateSpatialIndex()

{
/* -------------------------------------------------------------------- */
/*      For new layers we try to create a spatial index.                */
/* -------------------------------------------------------------------- */
    if( bNewLayer && sExtent.IsInit() )
    {
/* -------------------------------------------------------------------- */
/*      If the user has disabled INDEX support then don't create the    */
/*      index.                                                          */
/* -------------------------------------------------------------------- */
        if( !CPLFetchBool( papszOptions, "SPATIAL_INDEX", true ) ||
            !CPLFetchBool( papszOptions, "INDEX", true ) )
            return;

/* -------------------------------------------------------------------- */
/*      Establish an index name.  For some reason Oracle 8.1.7 does     */
/*      not support spatial index names longer than 18 characters so    */
/*      we magic up an index name if it would be too long.              */
/* -------------------------------------------------------------------- */
        char  szIndexName[20];

        if( strlen(poFeatureDefn->GetName()) < 15 )
            snprintf( szIndexName, sizeof(szIndexName), "%s_idx", poFeatureDefn->GetName() );
        else if( strlen(poFeatureDefn->GetName()) < 17 )
            snprintf( szIndexName, sizeof(szIndexName), "%si", poFeatureDefn->GetName() );
        else
        {
            int i, nHash = 0;
            const char *pszSrcName = poFeatureDefn->GetName();

            for( i = 0; pszSrcName[i] != '\0'; i++ )
                nHash = (nHash + i * pszSrcName[i]) % 987651;

            snprintf( szIndexName, sizeof(szIndexName), "OSI_%d", nHash );
        }

        poDS->GetSession()->CleanName( szIndexName );

/* -------------------------------------------------------------------- */
/*      Try creating an index on the table now.  Use a simple 5         */
/*      level quadtree based index.  Would R-tree be a better default?  */
/* -------------------------------------------------------------------- */
        OGROCIStringBuf  sIndexCmd;
        OGROCIStatement oExecStatement( poDS->GetSession() );

        sIndexCmd.Appendf( 10000, "CREATE INDEX \"%s\" ON %s(\"%s\") "
                           "INDEXTYPE IS MDSYS.SPATIAL_INDEX ",
                           szIndexName,
                           poFeatureDefn->GetName(),
                           pszGeomName );

        int bAddLayerGType = CPLTestBool(
            CSLFetchNameValueDef( papszOptions, "ADD_LAYER_GTYPE", "YES") ) &&
            GetGeomType() != wkbUnknown;

        CPLString osParams(CSLFetchNameValueDef(papszOptions,"INDEX_PARAMETERS", ""));
        if( bAddLayerGType || !osParams.empty() )
        {
            sIndexCmd.Append( " PARAMETERS( '" );
            if( !osParams.empty() )
                sIndexCmd.Append( osParams.c_str() );
            if( bAddLayerGType &&
                osParams.ifind("LAYER_GTYPE") == std::string::npos )
            {
                if( !osParams.empty() )
                    sIndexCmd.Append( ", " );
                sIndexCmd.Append( "LAYER_GTYPE=" );
                if( wkbFlatten(GetGeomType()) == wkbPoint )
                    sIndexCmd.Append( "POINT" );
                else if( wkbFlatten(GetGeomType()) == wkbLineString )
                    sIndexCmd.Append( "LINE" );
                else if( wkbFlatten(GetGeomType()) == wkbPolygon )
                    sIndexCmd.Append( "POLYGON" );
                else if( wkbFlatten(GetGeomType()) == wkbMultiPoint )
                    sIndexCmd.Append( "MULTIPOINT" );
                else if( wkbFlatten(GetGeomType()) == wkbMultiLineString )
                    sIndexCmd.Append( "MULTILINE" );
                else if( wkbFlatten(GetGeomType()) == wkbMultiPolygon )
                    sIndexCmd.Append( "MULTIPOLYGON" );
                else
                    sIndexCmd.Append( "COLLECTION" );
            }
            sIndexCmd.Append( "' )" );
        }

        if( oExecStatement.Execute( sIndexCmd.GetString() ) != CE_None )
        {
            CPLString osDropCommand;
            osDropCommand.Printf( "DROP INDEX \"%s\"", szIndexName );
            oExecStatement.Execute( osDropCommand );
        }
    }
}

int OGROCITableLayer::GetMaxFID()
{
    if( nFirstId > 0 )
        return nFirstId - 1;

    if( pszFIDName == nullptr )
        return 0;

    OGROCIStringBuf sCmd;
    OGROCIStatement oSelect( poDS->GetSession() );

    sCmd.Appendf( 10000, "SELECT MAX(\"%s\") FROM \"%s\"",
            pszFIDName,
            poFeatureDefn->GetName()
            );

    oSelect.Execute( sCmd.GetString() );

    char **papszResult = oSelect.SimpleFetchRow();
    return CSLCount(papszResult) == 1 ? atoi( papszResult[0] ) : 0;
}
