/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Implements OGRSDELayer class.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2008, Shawn Gervais <project10@project10.net>
 * Copyright (c) 2008, Howard Butler <hobu.inc@gmail.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_sde.h"
#include "cpl_conv.h"
#include "cpl_string.h"

CPL_CVSID("$Id: ogrsdelayer.cpp 8e5eeb35bf76390e3134a4ea7076dab7d478ea0e 2018-11-14 22:55:13 +0100 Even Rouault $")

/************************************************************************/
/*                            OGRSDELayer()                             */
/************************************************************************/

OGRSDELayer::OGRSDELayer( OGRSDEDataSource *poDSIn, int bUpdate )

{
    poDS = poDSIn;

    bUpdateAccess = bUpdate;
    bPreservePrecision = TRUE;

    iFIDColumn = -1;
    nNextFID = 0;
    iNextFIDToWrite = 1;

    iShapeColumn = -1;
    poSRS = NULL;
    poFeatureDefn = NULL;

    bQueryInstalled = FALSE;
    hStream = NULL;
    hCoordRef = NULL;
    papszAllColumns = NULL;
    bHaveLayerInfo = FALSE;
    bUseNSTRING = FALSE;

    pszOwnerName = NULL;
    pszDbTableName = NULL;
    bVersioned = FALSE;
    bQueryActive = FALSE;
}

/************************************************************************/
/*                            ~OGRSDELayer()                            */
/************************************************************************/

OGRSDELayer::~OGRSDELayer()

{
    if( m_nFeaturesRead > 0 && poFeatureDefn != NULL )
    {
        CPLDebug( "OGR_SDE", "%d features read on layer '%s'.",
                  (int) m_nFeaturesRead,
                  poFeatureDefn->GetName() );
    }

    if( bHaveLayerInfo )
        SE_layerinfo_free( hLayerInfo );

    if( hStream )
    {
        SE_stream_free( hStream );
        hStream = NULL;
    }

    if( poFeatureDefn )
        poFeatureDefn->Release();

    if( hCoordRef )
        SE_coordref_free( hCoordRef );

    if( poSRS )
        poSRS->Release();

    CSLDestroy( papszAllColumns );

    CPLFree( pszOwnerName );
    CPLFree( pszDbTableName );
}

/************************************************************************/
/*                             Initialize()                             */
/************************************************************************/

int OGRSDELayer::Initialize( const char *pszTableName,
                             const char *pszFIDColumn,
                             const char *pszShapeColumn )

{
    SE_COLUMN_DEF *asColumnDefs;
    SHORT   nColumnCount;
    int nSDEErr, iCol;

/* -------------------------------------------------------------------- */
/*      Determine DBMS table owner name and the table name part         */
/*      from pszTableName which is a fully-qualified table name         */
/* -------------------------------------------------------------------- */
    char               *pszTableNameCopy = CPLStrdup( pszTableName );
    char               *pszPeriodPtr;

    if( (pszPeriodPtr = strstr( pszTableNameCopy,"." )) != NULL )
    {
        *pszPeriodPtr  = '\0';
        pszOwnerName = CPLStrdup(pszTableNameCopy);
        pszDbTableName = CPLStrdup(pszPeriodPtr + 1);
    }
    else
    {
        pszOwnerName   = NULL;
        pszDbTableName = CPLStrdup(pszTableName);
    }

    CPLFree( pszTableNameCopy );

/* -------------------------------------------------------------------- */
/*      Determine whether multi-versioning is enabled for this table.   */
/* -------------------------------------------------------------------- */
    SE_REGINFO          hRegInfo;

    nSDEErr = SE_reginfo_create( &hRegInfo );
    if( nSDEErr != SE_SUCCESS )
    {
        poDS->IssueSDEError( nSDEErr, "SE_reginfo_create" );
        return FALSE;
    }

    // TODO: This is called from places that have RegInfo already -
    // should we just pass that in?
    nSDEErr = SE_registration_get_info( poDS->GetConnection(),
                                        pszTableName,
                                        hRegInfo );
    if( nSDEErr != SE_SUCCESS )
    {
        poDS->IssueSDEError( nSDEErr, "SE_registration_get_info" );
        return FALSE;
    }

    bVersioned = SE_reginfo_is_multiversion( hRegInfo );

/* -------------------------------------------------------------------- */
/*      Describe table                                                  */
/* -------------------------------------------------------------------- */
    nSDEErr =
        SE_table_describe( poDS->GetConnection(), pszTableName,
                           &nColumnCount, &asColumnDefs );

    if( nSDEErr != SE_SUCCESS )
    {
        poDS->IssueSDEError( nSDEErr, "SE_table_describe" );
        return FALSE;
    }

    poFeatureDefn = new OGRFeatureDefn( pszTableName );
    SetDescription( poFeatureDefn->GetName() );
    poFeatureDefn->Reference();

/* -------------------------------------------------------------------- */
/*  If the OGR_SDE_GETLAYERTYPE option is set to TRUE, then the layer   */
/*  is tested to see if it contains one, and only one, type of geometry */
/*  then that geometry type is placed into the LayerDefn.               */
/* -------------------------------------------------------------------- */
    const char *pszLayerType = CPLGetConfigOption( "OGR_SDE_GETLAYERTYPE",
                                                   "FALSE" );

    if( CPLTestBool(pszLayerType) )
    {
        poFeatureDefn->SetGeomType(DiscoverLayerType());
    }

    for( iCol = 0; iCol < nColumnCount; iCol++ )
    {
        OGRFieldType eOGRType = OFTIntegerList; // dummy
        int nWidth = -1, nPrecision = -1;

        papszAllColumns = CSLAddString( papszAllColumns,
                                        asColumnDefs[iCol].column_name );

        switch( asColumnDefs[iCol].sde_type )
        {
          case SE_SMALLINT_TYPE:
          case SE_INTEGER_TYPE:
            eOGRType = OFTInteger;
            nWidth = asColumnDefs[iCol].size;
            break;

          case SE_FLOAT_TYPE:
          case SE_DOUBLE_TYPE:
            eOGRType = OFTReal;
            nWidth = asColumnDefs[iCol].size;
            nPrecision = asColumnDefs[iCol].decimal_digits;
            break;

          case SE_STRING_TYPE:
#ifdef SE_UUID_TYPE
          case SE_UUID_TYPE:
#endif
#ifdef SE_NSTRING_TYPE
          case SE_NSTRING_TYPE:
#endif
#ifdef SE_CLOB_TYPE
          case SE_CLOB_TYPE:
#endif
#ifdef SE_NCLOB_TYPE
          case SE_NCLOB_TYPE:
#endif
            eOGRType = OFTString;
            nWidth = asColumnDefs[iCol].size;
            break;

          case SE_BLOB_TYPE:
            eOGRType = OFTBinary;
            break;

          case SE_DATE_TYPE:
            eOGRType = OFTDateTime;
            break;

          case SE_SHAPE_TYPE:
          {
              if( iShapeColumn == -1 )
              {
                  if( pszShapeColumn == NULL
                      || EQUAL(pszShapeColumn,
                               asColumnDefs[iCol].column_name) )
                  {
                      iShapeColumn = iCol;
                      osShapeColumnName = asColumnDefs[iCol].column_name;
                  }
              }
          }
          break;

          default:
            break;
        }

        if( eOGRType == OFTIntegerList )
            continue;

        OGRFieldDefn  oField( asColumnDefs[iCol].column_name, eOGRType );

        if( nWidth != -1 )
            oField.SetWidth( nWidth );
        if( nPrecision != -1 )
            oField.SetPrecision( nPrecision );

        poFeatureDefn->AddFieldDefn( &oField );

        anFieldMap.push_back( iCol );
        anFieldTypeMap.push_back( asColumnDefs[iCol].sde_type );

        if( pszFIDColumn
            && EQUAL(asColumnDefs[iCol].column_name,pszFIDColumn) )
        {
            osFIDColumnName = asColumnDefs[iCol].column_name;
            iFIDColumn = anFieldMap.size() - 1;
        }
    }

    SE_table_free_descriptions( asColumnDefs );
    SE_reginfo_free( hRegInfo );

    return TRUE;
}

/************************************************************************/
/*                           NeedLayerInfo()                            */
/*                                                                      */
/*      Verify layerinfo is available, and load if not.  Loading        */
/*      layerinfo is relatively expensive so we try to put it off as    */
/*      long as possible.                                               */
/************************************************************************/

int OGRSDELayer::NeedLayerInfo()

{
    int nSDEErr;

    if( bHaveLayerInfo )
        return TRUE;

    nSDEErr = SE_layerinfo_create( NULL, &hLayerInfo );
    if( nSDEErr != SE_SUCCESS )
    {
        poDS->IssueSDEError( nSDEErr, "SE_layerinfo_create" );
        return FALSE;
    }

    CPLDebug( "OGR_SDE", "Loading %s layerinfo.",
              poFeatureDefn->GetName() );

    nSDEErr = SE_layer_get_info( poDS->GetConnection(),
                                 poFeatureDefn->GetName(),
                                 osShapeColumnName.c_str(),
                                 hLayerInfo );
    if( nSDEErr != SE_SUCCESS )
    {
        poDS->IssueSDEError( nSDEErr, "SE_layer_get_info" );
        return FALSE;
    }

    bHaveLayerInfo = TRUE;

/* -------------------------------------------------------------------- */
/*      Fetch coordinate reference system.                              */
/* -------------------------------------------------------------------- */
    SE_coordref_create( &hCoordRef );

    nSDEErr = SE_layerinfo_get_coordref( hLayerInfo, hCoordRef );
    if( nSDEErr != SE_SUCCESS )
    {
        poDS->IssueSDEError( nSDEErr, "SE_layerinfo_get_coordref" );
    }
    else
    {
        char szWKT[SE_MAX_SPATIALREF_SRTEXT_LEN];

        nSDEErr = SE_coordref_get_description( hCoordRef, szWKT );
        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_coordref_get_description" );
        }
        else
        {
            poSRS = new OGRSpatialReference(szWKT);
            poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
        }

        LFLOAT falsex, falsey, xyunits;
        nSDEErr = SE_coordref_get_xy( hCoordRef, &falsex, &falsey, &xyunits );
        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_coordref_get_xy" );
        }
        else
        {
            CPLDebug( "SDE", "SE_coordref_get_xy(%s) = %g/%g/%g",
                    pszDbTableName, falsex, falsey, xyunits );
        }
    }

    return TRUE;
}

/************************************************************************/
/*                          DiscoverLayerType()                         */
/************************************************************************/

OGRwkbGeometryType OGRSDELayer::DiscoverLayerType()
{
    if( !NeedLayerInfo() )
        return wkbUnknown;

    int nSDEErr;
    LONG nShapeTypeMask = 0;

/* -------------------------------------------------------------------- */
/*      Check layerinfo flags to establish what geometry types may      */
/*      occur.                                                          */
/* -------------------------------------------------------------------- */
    nSDEErr = SE_layerinfo_get_shape_types( hLayerInfo, &nShapeTypeMask );
    if (nSDEErr != SE_SUCCESS)
    {
        CPLDebug( "OGR_SDE",
                  "Unable to read the layer type information, defaulting to wkbUnknown:  error=%d.",
                  nSDEErr );

        return wkbUnknown;
    }

    int bIsMultipart = (nShapeTypeMask & SE_MULTIPART_TYPE_MASK) ? 1 : 0;
    nShapeTypeMask &= ~SE_MULTIPART_TYPE_MASK;

    // Since we assume that all layers can bear a NULL geometry,
    // throw the flag away.
    nShapeTypeMask &= ~SE_NIL_TYPE_MASK;

    int nTypeCount = 0;
    if ( nShapeTypeMask & SE_POINT_TYPE_MASK )
        nTypeCount++;
    if ( nShapeTypeMask & SE_LINE_TYPE_MASK
         || nShapeTypeMask & SE_SIMPLE_LINE_TYPE_MASK )
        nTypeCount++;

    if ( nShapeTypeMask & SE_AREA_TYPE_MASK )
        nTypeCount++;

/* -------------------------------------------------------------------- */
/*      When the flags indicate multiple geometry types are             */
/*      possible, we examine the layer statistics to see if in          */
/*      reality only one geometry type does occur.  This is somewhat    */
/*      expensive, so we avoid it if we can.                            */
/* -------------------------------------------------------------------- */
    if ( nTypeCount == 0 )
    {
        CPLDebug( "OGR_SDE", "There is no layer type indicated for the current layer." );
        return wkbUnknown;
    }
    else if ( nTypeCount > 1 )
    {
        CPLDebug( "OGR_SDE", "More than one layer type is indicated for this layer, gathering layer statistics are being gathered." );
        SE_LAYER_STATS layerstats = {0};
        char szTableName[SE_QUALIFIED_TABLE_NAME];
        char szShapeColumn[SE_MAX_COLUMN_LEN];

        szTableName[0] = '\0';
        szShapeColumn[0] = '\0';

        nSDEErr = SE_layerinfo_get_spatial_column( hLayerInfo, szTableName,
                                                   szShapeColumn );
        if ( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_layerinfo_get_spatial_column" );
            return wkbUnknown;
        }

        nSDEErr = SE_layer_get_statistics( poDS->GetConnection(), szTableName,
                                           szShapeColumn,
                                           &layerstats);
        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_layer_get_statistics" );
            return wkbUnknown;
        }

        if ( nShapeTypeMask & SE_POINT_TYPE_MASK &&
             ( layerstats.POINTs + layerstats.MultiPOINTs ) == 0 )
            nShapeTypeMask &= ~SE_POINT_TYPE_MASK;

        if ( nShapeTypeMask & SE_LINE_TYPE_MASK &&
             ( layerstats.LINEs + layerstats.MultiLINEs ) == 0 )
            nShapeTypeMask &= ~SE_LINE_TYPE_MASK;

        if ( nShapeTypeMask & SE_SIMPLE_LINE_TYPE_MASK &&
             ( layerstats.SIMPLE_LINEs + layerstats.MultiSIMPLE_LINEs ) == 0 )
            nShapeTypeMask &= ~SE_SIMPLE_LINE_TYPE_MASK;

        if ( nShapeTypeMask & SE_AREA_TYPE_MASK &&
             ( layerstats.AREAs + layerstats.MultiAREAs ) == 0 )
            nShapeTypeMask &= ~SE_AREA_TYPE_MASK;
    }

/* -------------------------------------------------------------------- */
/*      Select a geometry type based on the remaining flags.  If        */
/*      there is a mix we will fall through to the default (wkbUnknown). */
/* -------------------------------------------------------------------- */
    OGRwkbGeometryType eGeoType;
    char *pszTypeName;
    switch (nShapeTypeMask)
    {
      case SE_POINT_TYPE_MASK:
        if (bIsMultipart)
            eGeoType = wkbMultiPoint;
        else
            eGeoType = wkbPoint;
        pszTypeName = "point";
        break;

      case (SE_SIMPLE_LINE_TYPE_MASK | SE_LINE_TYPE_MASK):
      case SE_SIMPLE_LINE_TYPE_MASK:
      case SE_LINE_TYPE_MASK:
        if (bIsMultipart)
            eGeoType = wkbMultiLineString;
        else
            eGeoType = wkbLineString;
        pszTypeName = "line";
        break;

      case SE_AREA_TYPE_MASK:
        if (bIsMultipart)
            eGeoType = wkbMultiPolygon;
        else
            eGeoType = wkbPolygon;
        pszTypeName = "polygon";
        break;

      default:
        eGeoType = wkbUnknown;
        pszTypeName = "unknown";
        break;
    }

    CPLDebug( "OGR_SDE",
              "DiscoverLayerType is returning type=%d (%s), multipart=%d.",
              eGeoType, pszTypeName, bIsMultipart );

    return eGeoType;
}

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

void OGRSDELayer::ResetReading()

{
    bQueryInstalled = FALSE;
    nNextFID = 0;
}

/************************************************************************/
/*                            InstallQuery()                            */
/*                                                                      */
/*      Setup the stream with current query characteristics.            */
/************************************************************************/

int OGRSDELayer::InstallQuery( int bCountingOnly )

{
    int nSDEErr;

/* -------------------------------------------------------------------- */
/*      Create stream, or reset it.                                     */
/* -------------------------------------------------------------------- */
    if( ResetStream() != OGRERR_NONE )
        return FALSE;

/* -------------------------------------------------------------------- */
/*      Create query info.                                              */
/* -------------------------------------------------------------------- */
    SE_QUERYINFO hQueryInfo;
    const char *pszTableName = poFeatureDefn->GetName();

    nSDEErr = SE_queryinfo_create (&hQueryInfo);
    if( nSDEErr != SE_SUCCESS) {
        poDS->IssueSDEError( nSDEErr, "SE_queryinfo_create" );
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Select table.                                                   */
/* -------------------------------------------------------------------- */
    nSDEErr = SE_queryinfo_set_tables( hQueryInfo, 1,
                                       &pszTableName, NULL );
    if( nSDEErr != SE_SUCCESS) {
        poDS->IssueSDEError( nSDEErr, "SE_queryinfo_set_tables" );
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Set where clause.                                               */
/* -------------------------------------------------------------------- */
    nSDEErr = SE_queryinfo_set_where_clause( hQueryInfo,
                                             osAttributeFilter.c_str() );
    if( nSDEErr != SE_SUCCESS) {
        poDS->IssueSDEError( nSDEErr, "SE_queryinfo_set_where_clause" );
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      We want to join the spatial and attribute tables.               */
/* -------------------------------------------------------------------- */
    nSDEErr = SE_queryinfo_set_query_type(hQueryInfo,SE_QUERYTYPE_JSF);
    if( nSDEErr != SE_SUCCESS) {
        poDS->IssueSDEError( nSDEErr, "SE_queryinfo_set_query_type" );
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Establish the columns to query.  If only counting features,     */
/*      we will just use the FID column, otherwise we use all           */
/*      columns.                                                        */
/* -------------------------------------------------------------------- */
    if( bCountingOnly && iFIDColumn != -1 )
    {
        const char *pszFIDColName =
            poFeatureDefn->GetFieldDefn( iFIDColumn )->GetNameRef();

        nSDEErr = SE_queryinfo_set_columns( hQueryInfo, 1,
                                            (const char **) &pszFIDColName );
        if( nSDEErr != SE_SUCCESS) {
            poDS->IssueSDEError( nSDEErr, "SE_queryinfo_set_columns" );
            return FALSE;
        }
    }
    else
    {
        nSDEErr = SE_queryinfo_set_columns( hQueryInfo,
                                            CSLCount(papszAllColumns),
                                            (const char **) papszAllColumns );
        if( nSDEErr != SE_SUCCESS) {
            poDS->IssueSDEError( nSDEErr, "SE_queryinfo_set_columns" );
            return FALSE;
        }
    }

/* -------------------------------------------------------------------- */
/*      Apply the query to the stream.                                  */
/* -------------------------------------------------------------------- */
    nSDEErr = SE_stream_query_with_info( hStream, hQueryInfo );
    if( nSDEErr != SE_SUCCESS) {
        poDS->IssueSDEError( nSDEErr, "SE_stream_query_with_info" );
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Free query resources.                                           */
/* -------------------------------------------------------------------- */
    SE_queryinfo_free( hQueryInfo );

/* -------------------------------------------------------------------- */
/*      Setup spatial filter on stream if one is installed.             */
/* -------------------------------------------------------------------- */
    if( m_poFilterGeom != NULL )
    {
        SE_FILTER sConstraint;
        SE_ENVELOPE sEnvelope;
        SE_ENVELOPE  sLayerEnvelope;
        SE_SHAPE hRectShape;
        SHORT nSearchOrder = SE_SPATIAL_FIRST;

        if( !osAttributeFilter.empty() )
        {
            const char *pszOrder = CPLGetConfigOption( "OGR_SDE_SEARCHORDER",
                                                       "ATTRIBUTE_FIRST" );

            if( EQUAL(pszOrder, "ATTRIBUTE_FIRST") )
                nSearchOrder = SE_ATTRIBUTE_FIRST;
            else
            {
                if( !EQUAL(pszOrder, "SPATIAL_FIRST") )
                    CPLError( CE_Warning, CPLE_AppDefined,
                              "Unrecognized OGR_SDE_SEARCHORDER value of %s.",
                              pszOrder );
                nSearchOrder = SE_SPATIAL_FIRST;
            }
        }

        NeedLayerInfo(); // need hCoordRef

        nSDEErr = SE_shape_create( hCoordRef, &hRectShape );
        if( nSDEErr != SE_SUCCESS)
        {
            poDS->IssueSDEError( nSDEErr, "SE_shape_create");
            return FALSE;
        }
        sEnvelope.minx = m_sFilterEnvelope.MinX;
        sEnvelope.miny = m_sFilterEnvelope.MinY;
        sEnvelope.maxx = m_sFilterEnvelope.MaxX;
        sEnvelope.maxy = m_sFilterEnvelope.MaxY;

        nSDEErr = SE_layerinfo_get_envelope( hLayerInfo, &sLayerEnvelope );
        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_layerinfo_get_envelope" );
            return FALSE;
        }
        // ensure that the spatial filter overlaps area of the layer
        if (sEnvelope.minx > sLayerEnvelope.maxx || sEnvelope.maxx < sLayerEnvelope.minx ||
            sEnvelope.miny > sLayerEnvelope.maxy || sEnvelope.maxy < sLayerEnvelope.miny )
        {
            // using a small rectangle to filter out all the shapes
            sEnvelope.minx = sLayerEnvelope.minx;
            sEnvelope.miny = sLayerEnvelope.miny;
            sEnvelope.maxx = sLayerEnvelope.minx + 0.00000001;
            sEnvelope.maxy = sLayerEnvelope.miny + 0.00000001;
        }

        nSDEErr = SE_shape_generate_rectangle( &sEnvelope, hRectShape );
        if( nSDEErr != SE_SUCCESS)
        {
            poDS->IssueSDEError( nSDEErr, "SE_shape_generate_rectangle");
            return FALSE;
        }

        sConstraint.filter.shape = hRectShape;
        strcpy( sConstraint.table, poFeatureDefn->GetName() );
        strcpy( sConstraint.column, osShapeColumnName.c_str() );
        sConstraint.method = SM_ENVP;
        sConstraint.filter_type = SE_SHAPE_FILTER;
        sConstraint.truth = TRUE;

        nSDEErr = SE_stream_set_spatial_constraints( hStream, nSearchOrder,
                                                     FALSE, 1, &sConstraint );
        if( nSDEErr != SE_SUCCESS)
        {
            poDS->IssueSDEError( nSDEErr, "SE_stream_set_spatial_constraints");
            return FALSE;
        }

        SE_shape_free( hRectShape );
    }

/* -------------------------------------------------------------------- */
/*      Execute the query.                                              */
/* -------------------------------------------------------------------- */
    nSDEErr = SE_stream_execute( hStream );
    if( nSDEErr != SE_SUCCESS ) {
        poDS->IssueSDEError( nSDEErr, "SE_stream_execute" );
        return FALSE;
    }

    bQueryInstalled = TRUE;

    return TRUE;
}

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

OGRErr OGRSDELayer::SetAttributeFilter( const char *pszQuery )

{
    CPLFree(m_pszAttrQueryString);
    m_pszAttrQueryString = (pszQuery) ? CPLStrdup(pszQuery) : NULL;

    if( pszQuery == NULL )
        osAttributeFilter = "";
    else
        osAttributeFilter = pszQuery;

    ResetReading();

    return OGRERR_NONE;
}

/************************************************************************/
/*                        TranslateOGRRecord()                          */
/*                                                                      */
/*      Translates OGR feature semantics to SDE ones and sets items     */
/*      in the stream for an update or insert operation. The stream is  */
/*      set in insert or update mode depending on the value of the      */
/*      bIsInsert flag. The stream must have already been reset by      */
/*      the caller. Actual execution of the stream operation is the     */
/*      responsibility of the caller.                                   */
/************************************************************************/
OGRErr OGRSDELayer::TranslateOGRRecord( OGRFeature *poFeature,
                                        int bIsInsert )

{
    SE_SHAPE            hShape;
    LONG                nSDEErr;

/* -------------------------------------------------------------------- */
/*      Translate geometry to SDE geometry                              */
/* -------------------------------------------------------------------- */
    if( poFeature->GetGeometryRef() != NULL )
    {
        if( TranslateOGRGeometry( poFeature->GetGeometryRef(), &hShape,
                                  hCoordRef ) != OGRERR_NONE )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Failed to convert geometry from OGR -> SDE");
            return OGRERR_FAILURE;
        }
    }

/* -------------------------------------------------------------------- */
/*      Determine which fields to insert                                */
/* -------------------------------------------------------------------- */
    int                *paiColToDefMap;
    char              **papszInsertCols = NULL;
    int                 nAttributeCols = 0;
    int                 nSpecialCols = 0;
    int                 i;

    paiColToDefMap = (int *) CPLMalloc( sizeof(int)
                                        * poFeatureDefn->GetFieldCount() );

    /*
     * If the row id is managed by USER, and not SDE, then we need to take
     * care to set the FID column ourselves. If the row id column is managed by
     * SDE we are forbidden from setting it.
     */
    if( nFIDColumnType == SE_REGISTRATION_ROW_ID_COLUMN_TYPE_USER
        && iFIDColumn != -1 )
    {
        papszInsertCols = CSLAddString( papszInsertCols,
                                        osFIDColumnName.c_str() );

        nSpecialCols++;
    }

    if( poFeature->GetGeometryRef() != NULL )
    {
        papszInsertCols = CSLAddString( papszInsertCols,
                                        osShapeColumnName.c_str() );

        nSpecialCols++;
    }

    // Add attribute fields of this feature, build mapping of
    // indexes to field definitions vs. the columns we will insert
    for( i=0; i < poFeatureDefn->GetFieldCount(); i++ )
    {
        OGRFieldDefn   *poFieldDefn = poFeatureDefn->GetFieldDefn(i);

        if( !poFeature->IsFieldSetAndNotNull(i) )
            continue;

        // Skip FID and Geometry columns
        if( EQUAL(poFieldDefn->GetNameRef(), osFIDColumnName.c_str()) )
        {
            // Skip the column if it is managed by SDE
            if( nFIDColumnType == SE_REGISTRATION_ROW_ID_COLUMN_TYPE_SDE )
                continue;
        }

        if( EQUAL(poFieldDefn->GetNameRef(), osShapeColumnName.c_str()) )
            continue;

        papszInsertCols = CSLAddString( papszInsertCols,
                                        poFieldDefn->GetNameRef() );

        paiColToDefMap[nAttributeCols] = i;
        nAttributeCols++;
    }

/* -------------------------------------------------------------------- */
/*      Prepare the insert or update stream mode                        */
/* -------------------------------------------------------------------- */
    const char         *pszMethod;

    if( bIsInsert )
    {
        nSDEErr = SE_stream_insert_table( hStream, poFeatureDefn->GetName(),
                                          nSpecialCols + nAttributeCols,
                                          (const char **)papszInsertCols );
        pszMethod = "SE_stream_insert_table";
    }
    else // It's an UPDATE
    {
        const char     *pszWhere;

        // Check that there is a FID column detected on the table, and that
        // this feature has a non-null FID
        if( iFIDColumn == -1 )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Cannot update feature: Layer \"%s\" has no FID column",
                      poFeatureDefn->GetName() );

            CSLDestroy( papszInsertCols );
            CPLFree( paiColToDefMap );

            return OGRERR_FAILURE;
        }
        else if( poFeature->GetFID() == OGRNullFID )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Cannot update feature: Feature has a NULL Feature ID" );

            CSLDestroy( papszInsertCols );
            CPLFree( paiColToDefMap );

            return OGRERR_FAILURE;
        }

        // Build WHERE clause
        pszWhere = CPLSPrintf( "%s = %ld", osFIDColumnName.c_str(),
                                           poFeature->GetFID() );

        nSDEErr = SE_stream_update_table( hStream, poFeatureDefn->GetName(),
                                          nSpecialCols + nAttributeCols,
                                          (const char **)papszInsertCols,
                                          pszWhere );

        pszMethod = "SE_stream_update_table";
    }

    if( nSDEErr != SE_SUCCESS )
    {
        poDS->IssueSDEError( nSDEErr, pszMethod );
        return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Set the feature attributes                                      */
/* -------------------------------------------------------------------- */
    short               iCurColNum = 1;

    if( nFIDColumnType == SE_REGISTRATION_ROW_ID_COLUMN_TYPE_USER
        && iFIDColumn != -1 )
    {
        LONG            nFID;

        nFID = poFeature->GetFID();
        if( nFID == OGRNullFID )
        {
            nFID = iNextFIDToWrite++;
            poFeature->SetFID( nFID );
        }

        nSDEErr = SE_stream_set_integer( hStream, iCurColNum++,
                                         &nFID );
        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_stream_set_integer" );

            CSLDestroy( papszInsertCols );
            CPLFree( paiColToDefMap );

            return OGRERR_FAILURE;
        }
    }

    // Set geometry (shape) column
    if( poFeature->GetGeometryRef() != NULL )
    {
        nSDEErr = SE_stream_set_shape( hStream, iCurColNum++, hShape );

        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_stream_set_shape" );

            CSLDestroy( papszInsertCols );
            CPLFree( paiColToDefMap );
            SE_shape_free( hShape );
            return OGRERR_FAILURE;
        }
    }

    // Set attribute columns
    for( i=0; i < nAttributeCols; i++ )
    {
        int             iFieldDefnIdx = paiColToDefMap[i];
        OGRFieldDefn   *poFieldDefn;
        OGRField       *poField;

        poFieldDefn = poFeatureDefn->GetFieldDefn(iFieldDefnIdx);
        poField = poFeature->GetRawFieldRef(iFieldDefnIdx);

        CPLAssert( poFieldDefn != NULL );
        CPLAssert( poField != NULL );

        if( poFieldDefn->GetType() == OFTInteger )
        {
            LONG        nLong = poField->Integer;

            nSDEErr = SE_stream_set_integer( hStream, iCurColNum++,
                                             &nLong );
            if( nSDEErr != SE_SUCCESS )
            {
                poDS->IssueSDEError( nSDEErr, "SE_stream_set_integer" );
                CSLDestroy( papszInsertCols );
                CPLFree( paiColToDefMap );
                return OGRERR_FAILURE;
            }
        }

        else if( poFieldDefn->GetType() == OFTReal )
        {
            LFLOAT      nDouble = poField->Real;

            nSDEErr = SE_stream_set_double( hStream, iCurColNum++,
                                            &nDouble );
            if( nSDEErr != SE_SUCCESS )
            {
                poDS->IssueSDEError( nSDEErr, "SE_stream_set_float" );
                CSLDestroy( papszInsertCols );
                CPLFree( paiColToDefMap );
                return OGRERR_FAILURE;
            }
        }

        else if( poFieldDefn->GetType() == OFTString
                 && anFieldTypeMap[iFieldDefnIdx] == SE_NSTRING_TYPE )
        {
            SE_WCHAR *pszUTF16 = (SE_WCHAR *)
                CPLRecodeToWChar( poField->String, CPL_ENC_UTF8,
                                  CPL_ENC_UTF16 );

            nSDEErr = SE_stream_set_nstring( hStream, iCurColNum++,
                                             pszUTF16 );
            CPLFree( pszUTF16 );

            if( nSDEErr != SE_SUCCESS )
            {
                poDS->IssueSDEError( nSDEErr, "SE_stream_set_nstring" );
                CSLDestroy( papszInsertCols );
                CPLFree( paiColToDefMap );
                return OGRERR_FAILURE;
            }
        }

        else if( poFieldDefn->GetType() == OFTString )
        {
            nSDEErr = SE_stream_set_string( hStream, iCurColNum++,
                                            poField->String );
            if( nSDEErr != SE_SUCCESS )
            {
                poDS->IssueSDEError( nSDEErr, "SE_stream_set_string" );
                CSLDestroy( papszInsertCols );
                CPLFree( paiColToDefMap );
                return OGRERR_FAILURE;
            }
        }

        else if( poFieldDefn->GetType() == OFTDate
                 || poFieldDefn->GetType() == OFTDateTime )
        {
            struct tm sDateVal;

            // TODO: hobu, please double-check this.
            sDateVal.tm_year  = poField->Date.Year - 1900;
            sDateVal.tm_mon   = poField->Date.Month - 1;
            sDateVal.tm_mday  = poField->Date.Day;
            sDateVal.tm_hour  = poField->Date.Hour;
            sDateVal.tm_min   = poField->Date.Minute;
            sDateVal.tm_sec   = poField->Date.Second;
            sDateVal.tm_isdst = (poField->Date.TZFlag == 0 ? 0 : 1);

            nSDEErr = SE_stream_set_date( hStream, iCurColNum++,
                                          &sDateVal );
            if( nSDEErr != SE_SUCCESS )
            {
                poDS->IssueSDEError( nSDEErr, "SE_stream_set_date" );
                CSLDestroy( papszInsertCols );
                CPLFree( paiColToDefMap );
                return OGRERR_FAILURE;
            }
        }

        else
        {
            CPLError( CE_Warning, CPLE_AppDefined,
                      "Cannot set attribute of type %s in SDE layer: "
                      "attempting to create as STRING",
                      OGRFieldDefn::GetFieldTypeName(poFieldDefn->GetType()) );

            CSLDestroy( papszInsertCols );
            CPLFree( paiColToDefMap );

            return OGRERR_FAILURE;
        }
    }

    CSLDestroy( papszInsertCols );
    CPLFree( paiColToDefMap );
    SE_shape_free(hShape);
    return OGRERR_NONE;
}

/************************************************************************/
/*                        TranslateOGRGeometry()                        */
/************************************************************************/
OGRErr OGRSDELayer::TranslateOGRGeometry( OGRGeometry *poGeom,
                                          SE_SHAPE *phShape,
                                          SE_COORDREF hCoordRef )

{
    LONG                nSDEErr;

    CPLAssert( poGeom != NULL );
    CPLAssert( phShape != NULL );

/* -------------------------------------------------------------------- */
/*      Initialize shape.                                               */
/* -------------------------------------------------------------------- */
    nSDEErr = SE_shape_create( hCoordRef, phShape );
    if( nSDEErr != SE_SUCCESS )
    {
        poDS->IssueSDEError( nSDEErr, "SE_shape_create" );
        return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Determine whether the geometry includes Z coordinates.          */
/* -------------------------------------------------------------------- */
    int                 b3D = FALSE;

    b3D = wkbHasZ(poGeom->getGeometryType());

/* -------------------------------------------------------------------- */
/*      Translate POINT/MULTIPOINT type.                                */
/* -------------------------------------------------------------------- */
    if( wkbFlatten(poGeom->getGeometryType()) == wkbPoint )
    {
        OGRPoint       *poPoint = (OGRPoint *) poGeom;
        LONG            nParts = 1;
        SE_POINT      *asPointParts;
        LFLOAT         *anfZcoords;

        asPointParts = (SE_POINT*) CPLMalloc(nParts * sizeof(SE_POINT));
        if (!asPointParts) {
            CPLError( CE_Fatal, CPLE_AppDefined,
                      "Cannot allocate asPointParts" );
            return OGRERR_FAILURE;
        }

        anfZcoords = (LFLOAT*) CPLMalloc(nParts * sizeof(LFLOAT));
        if (!anfZcoords) {
            CPLError( CE_Fatal, CPLE_AppDefined,
                      "Cannot allocate anfZcoords" );
            return OGRERR_FAILURE;
        }
        asPointParts[0].x = poPoint->getX();
        asPointParts[0].y = poPoint->getY();

        if( b3D )
        {
            anfZcoords[0] = poPoint->getZ();
            nSDEErr = SE_shape_generate_point( nParts, asPointParts, anfZcoords,
                                               NULL, *phShape );
        }
        else
        {
            nSDEErr = SE_shape_generate_point( nParts, asPointParts, NULL, NULL,
                                               *phShape );
        }

        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_shape_generate_point" );
            return OGRERR_FAILURE;
        }
    }

    else if( wkbFlatten(poGeom->getGeometryType()) == wkbMultiPoint )
    {
        OGRMultiPoint  *poMulPoint = (OGRMultiPoint *) poGeom;
        LONG            nParts;
        SE_POINT       *pasPointParts;
        LFLOAT         *panfZcoords = NULL;
        int             i;

        nParts = poMulPoint->getNumGeometries();

        pasPointParts = (SE_POINT *) CPLMalloc( sizeof(SE_POINT) * nParts );

        if( b3D )
            panfZcoords = (LFLOAT *) CPLMalloc( sizeof(LFLOAT) * nParts );

        for( i=0; i < nParts; i++ )
        {
            OGRPoint   *poThisPoint;

            poThisPoint = (OGRPoint *) poMulPoint->getGeometryRef(i);

            pasPointParts[i].x = poThisPoint->getX();
            pasPointParts[i].y = poThisPoint->getY();

            if( b3D )
                panfZcoords[i] = poThisPoint->getZ();
        }

        nSDEErr = SE_shape_generate_point( nParts, pasPointParts, panfZcoords,
                                           NULL, *phShape );

        CPLFree( pasPointParts );
        if( b3D )
            CPLFree( panfZcoords );

        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_shape_generate_point" );
            return OGRERR_FAILURE;
        }
    }

/* -------------------------------------------------------------------- */
/*      Translate POLYGON/MULTIPOLYGON type.                            */
/* -------------------------------------------------------------------- */
    else if( wkbFlatten(poGeom->getGeometryType()) == wkbPolygon )
    {
        OGRPolygon     *poPoly = (OGRPolygon *) poGeom;
        LONG            nPoints=0, iCurPoint=0;
        SE_POINT       *pasPoints;
        LFLOAT         *panfZcoords = NULL;
        int             i, j;

        // Get exterior ring
        OGRLinearRing  *poExtRing = poPoly->getExteriorRing();

        if( poExtRing == NULL )
        {
            // The polygon is empty
            // TODO: Does this mean that the shape is NULL, then?
            nSDEErr = SE_shape_make_nil( *phShape );
            if( nSDEErr != SE_SUCCESS )
            {
                poDS->IssueSDEError( nSDEErr, "SE_shape_make_nil" );
                return OGRERR_FAILURE;
            }
            return OGRERR_NONE;
        }

        // Get total number of points in polygon
        nPoints += poExtRing->getNumPoints();

        for( i=0; i < poPoly->getNumInteriorRings(); i++ )
            nPoints += poPoly->getInteriorRing(i)->getNumPoints();

        pasPoints = (SE_POINT *) CPLMalloc( sizeof(SE_POINT) * nPoints );

        if( b3D )
            panfZcoords = (LFLOAT *) CPLMalloc( sizeof(LFLOAT) * nPoints );

        for( i=0; i < poExtRing->getNumPoints(); i++ )
        {
            OGRPoint    oLRPnt;

            poExtRing->getPoint( i, &oLRPnt );

            pasPoints[iCurPoint].x = oLRPnt.getX();
            pasPoints[iCurPoint].y = oLRPnt.getY();

            if( b3D )
                panfZcoords[iCurPoint] = oLRPnt.getZ();

            iCurPoint++;
        }

        for( i=0; i < poPoly->getNumInteriorRings(); i++ )
        {
            OGRLinearRing   *poIntRing = poPoly->getInteriorRing(i);

            for( j=0; j < poIntRing->getNumPoints(); j++ )
            {
                OGRPoint    oLRPnt;

                poIntRing->getPoint( j, &oLRPnt );

                pasPoints[iCurPoint].x = oLRPnt.getX();
                pasPoints[iCurPoint].y = oLRPnt.getY();

                if( b3D )
                    panfZcoords[iCurPoint] = oLRPnt.getZ();

                iCurPoint++;
            }
        }

        nSDEErr = SE_shape_generate_polygon( nPoints, 1, NULL, pasPoints,
                                             panfZcoords, NULL, *phShape );

        CPLFree( pasPoints );
        if( b3D )
            CPLFree( panfZcoords );

        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_shape_generate_polygon" );
            return OGRERR_FAILURE;
        }
    }

    else if( wkbFlatten(poGeom->getGeometryType()) == wkbMultiPolygon )
    {
        OGRMultiPolygon *poMP = (OGRMultiPolygon *) poGeom;
        LONG             nPoints=0;
        LONG             nParts;
        LONG             iCurPoint=0;
        LONG            *panPartOffsets;
        SE_POINT        *pasPoints;
        LFLOAT          *panfZcoords = NULL;
        int              i, j, k;

        nParts = poMP->getNumGeometries();

        // Find total number of points
        for( i=0; i < nParts; i++ )
        {
            OGRPolygon      *poPoly = (OGRPolygon *) poMP->getGeometryRef(i);
            OGRLinearRing   *poExtRing = poPoly->getExteriorRing();

            nPoints += poExtRing->getNumPoints();

            for( j=0; j < poPoly->getNumInteriorRings(); j++ )
                nPoints += poPoly->getInteriorRing(j)->getNumPoints();
        }

        // Allocate points and part offset arrays
        pasPoints = (SE_POINT *) CPLMalloc( sizeof(SE_POINT) * nPoints );
        panPartOffsets = (LONG *) CPLMalloc( sizeof(LONG) * nParts );
        if( b3D )
            panfZcoords = (LFLOAT *) CPLMalloc( sizeof(LFLOAT) * nPoints );

        // Build arrays of points and part offsets
        for( i=0; i < nParts; i++ )
        {
            OGRPolygon      *poPoly = (OGRPolygon *) poMP->getGeometryRef(i);
            OGRLinearRing   *poExtRing = poPoly->getExteriorRing();

            panPartOffsets[i] = iCurPoint;

            for( j=0; j < poExtRing->getNumPoints(); j++ )
            {
                OGRPoint    oLRPnt;

                poExtRing->getPoint( j, &oLRPnt );

                pasPoints[iCurPoint].x = oLRPnt.getX();
                pasPoints[iCurPoint].y = oLRPnt.getY();

                if( b3D )
                    panfZcoords[iCurPoint] = oLRPnt.getZ();

                iCurPoint++;
            }

            for( j=0; j < poPoly->getNumInteriorRings(); j++ )
            {
                OGRLinearRing   *poIntRing = poPoly->getInteriorRing(j);

                for( k=0; k < poIntRing->getNumPoints(); k++ )
                {
                    OGRPoint    oLRPnt;

                    poIntRing->getPoint( k, &oLRPnt );

                    pasPoints[iCurPoint].x = oLRPnt.getX();
                    pasPoints[iCurPoint].y = oLRPnt.getY();

                    if( b3D )
                        panfZcoords[iCurPoint] = oLRPnt.getZ();

                    iCurPoint++;
                }
            }
        }

        nSDEErr = SE_shape_generate_polygon( nPoints, nParts, panPartOffsets,
                                             pasPoints, panfZcoords, NULL,
                                             *phShape );

        CPLFree( pasPoints );
        CPLFree( panPartOffsets );
        if( b3D )
            CPLFree( panfZcoords );

        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_shape_generate_polygon" );
            return OGRERR_FAILURE;
        }
    }

/* -------------------------------------------------------------------- */
/*      Translate LINESTRING/MULTILINESTRING type.                      */
/* -------------------------------------------------------------------- */
    else if( wkbFlatten(poGeom->getGeometryType()) == wkbLineString )
    {
        OGRLineString  *poLineString = (OGRLineString *) poGeom;
        LONG            nParts = 1;
        LONG            nPoints;
        SE_POINT       *pasPoints;
        LFLOAT         *panfZcoords = NULL;
        int             i;

        nPoints = poLineString->getNumPoints();

        pasPoints = (SE_POINT *) CPLMalloc( sizeof(SE_POINT) * nPoints );
        if( b3D )
            panfZcoords = (LFLOAT *) CPLMalloc( sizeof(LFLOAT) * nPoints );

        for( i=0; i < nPoints; i++ )
        {
            OGRPoint   oPoint;

            poLineString->getPoint( i, &oPoint );

            pasPoints[i].x = oPoint.getX();
            pasPoints[i].y = oPoint.getY();

            if( b3D )
                panfZcoords[i] = oPoint.getZ();
        }

        nSDEErr = SE_shape_generate_line( nPoints, nParts, NULL, pasPoints,
                                          panfZcoords, NULL, *phShape );

        CPLFree( pasPoints );
        if( b3D )
            CPLFree( panfZcoords );

        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_shape_generate_line" );
            return OGRERR_FAILURE;
        }
    }

    else if( wkbFlatten(poGeom->getGeometryType()) == wkbMultiLineString )
    {
        OGRMultiLineString *poMLS = (OGRMultiLineString *) poGeom;
        LONG            nParts;
        LONG            nPoints = 0;
        LONG            iCurPoint = 0;
        SE_POINT       *pasPoints;
        LONG           *panPartOffsets;
        LFLOAT         *panfZcoords = NULL;
        int             i, j;

        // Get number of parts and total number of points
        nParts = poMLS->getNumGeometries();

        for( i=0; i < nParts; i++ )
        {
            OGRLineString   *poLS = (OGRLineString *) poMLS->getGeometryRef(i);

            nPoints += poLS->getNumPoints();
        }

        // Allocate arrays for points and part offsets
        pasPoints = (SE_POINT *) CPLMalloc( sizeof(SE_POINT) * nPoints );
        panPartOffsets = (LONG *) CPLMalloc( sizeof(LONG) * nParts );
        if( b3D )
            panfZcoords = (LFLOAT *) CPLMalloc( sizeof(LFLOAT) * nPoints );

        for( i=0; i < nParts; i++ )
        {
            OGRLineString   *poLS = (OGRLineString *) poMLS->getGeometryRef(i);

            panPartOffsets[i] = iCurPoint;

            for( j=0; j < poLS->getNumPoints(); j++ )
            {
                OGRPoint    oPoint;

                poLS->getPoint( j, &oPoint );

                pasPoints[iCurPoint].x = oPoint.getX();
                pasPoints[iCurPoint].y = oPoint.getY();

                if( b3D )
                    panfZcoords[iCurPoint] = oPoint.getZ();

                iCurPoint++;
            }
        }

        nSDEErr = SE_shape_generate_line( nPoints, nParts, panPartOffsets,
                                          pasPoints, panfZcoords, NULL,
                                          *phShape );

        CPLFree( pasPoints );
        CPLFree( panPartOffsets );
        if( b3D )
            CPLFree( panfZcoords );

        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_shape_generate_line" );
            return OGRERR_FAILURE;
        }
    }

/* -------------------------------------------------------------------- */
/*       Error on other geometry types.                                 */
/* -------------------------------------------------------------------- */
    else
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "OGR_SDE: TranslateOGRGeometry() cannot translate "
                  "geometries of type %s (%d)", poGeom->getGeometryName(),
                  poGeom->getGeometryType() );

        return OGRERR_FAILURE;
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                        TranslateSDEGeometry()                        */
/************************************************************************/

OGRGeometry *OGRSDELayer::TranslateSDEGeometry( SE_SHAPE hShape )

{
    LONG nSDEGeomType;
    OGRGeometry *poGeom = NULL;

/* -------------------------------------------------------------------- */
/*      Fetch geometry type.                                            */
/* -------------------------------------------------------------------- */
    SE_shape_get_type( hShape, &nSDEGeomType );

    if( nSDEGeomType == SG_NIL_SHAPE )
        return NULL;

/* -------------------------------------------------------------------- */
/*      Fetch points and parts.                                         */
/* -------------------------------------------------------------------- */
    LONG nPointCount, nPartCount, nSubPartCount, nSDEErr;
    SE_POINT *pasPoints;
    LONG *panSubParts;
    LONG *panParts;
    LFLOAT *padfZ = NULL;

    SE_shape_get_num_points( hShape, 0, 0, &nPointCount );
    SE_shape_get_num_parts( hShape, &nPartCount, &nSubPartCount );

    pasPoints = (SE_POINT *) CPLMalloc(nPointCount * sizeof(SE_POINT));
    panParts = (LONG *) CPLMalloc(nPartCount * sizeof(LONG));
    panSubParts = (LONG *) CPLMalloc(nSubPartCount * sizeof(LONG));

    if( SE_shape_is_3D( hShape ) )
        padfZ = (LFLOAT *) CPLMalloc(nPointCount * sizeof(LFLOAT));

    nSDEErr = SE_shape_get_all_points( hShape, SE_DEFAULT_ROTATION,
                                       panParts, panSubParts,
                                       pasPoints, padfZ, NULL );
    if( nSDEErr != SE_SUCCESS )
    {
        poDS->IssueSDEError( nSDEErr, "SE_shape_get_all_points" );
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Handle simple point.                                            */
/* -------------------------------------------------------------------- */
    switch( nSDEGeomType )
    {
      case SG_POINT_SHAPE:
      {
          CPLAssert( nPointCount == 1 );
          CPLAssert( nSubPartCount == 1 );
          CPLAssert( nPartCount == 1 );
          if( padfZ )
              poGeom = new OGRPoint( pasPoints[0].x, pasPoints[0].y, padfZ[0] );
          else
              poGeom = new OGRPoint( pasPoints[0].x, pasPoints[0].y );
      }
      break;

/* -------------------------------------------------------------------- */
/*      Handle simple point.                                            */
/* -------------------------------------------------------------------- */
      case SG_MULTI_POINT_SHAPE:
      {
          OGRMultiPoint *poMP = new OGRMultiPoint();
          int iPart;

          CPLAssert( nPartCount == nSubPartCount ); // one vertex per point.
          CPLAssert( nPointCount == nPartCount );

          for( iPart = 0; iPart < nPartCount; iPart++ )
          {
              if( padfZ != NULL )
                  poMP->addGeometryDirectly( new OGRPoint( pasPoints[iPart].x,
                                                           pasPoints[iPart].y,
                                                           padfZ[iPart] ) );
              else
                  poMP->addGeometryDirectly( new OGRPoint( pasPoints[iPart].x,
                                                           pasPoints[iPart].y ) );
          }
          poGeom = poMP;
      }
      break;

/* -------------------------------------------------------------------- */
/*      Handle line.                                                    */
/* -------------------------------------------------------------------- */
      case SG_LINE_SHAPE:
      case SG_SIMPLE_LINE_SHAPE:
      {
          OGRLineString *poLine = new OGRLineString();
          int i;

          CPLAssert( nPartCount == 1 && nSubPartCount == 1 );

          poLine->setNumPoints( nPointCount );

          for( i = 0; i < nPointCount; i++ )
          {
              if( padfZ )
                  poLine->setPoint( i, pasPoints[i].x, pasPoints[i].y,
                                    padfZ[i] );
              else
                  poLine->setPoint( i, pasPoints[i].x, pasPoints[i].y );
          }

          poGeom = poLine;
      }
      break;

/* -------------------------------------------------------------------- */
/*      Handle multi line.                                              */
/* -------------------------------------------------------------------- */
      case SG_MULTI_LINE_SHAPE:
      case SG_MULTI_SIMPLE_LINE_SHAPE:
      {
          OGRMultiLineString *poMLS = new OGRMultiLineString();
          int iPart;

          CPLAssert( nPartCount == nSubPartCount );

          for( iPart = 0; iPart < nPartCount; iPart++ )
          {
              OGRLineString *poLine = new OGRLineString();
              int i, nLineVertCount;

              CPLAssert( panParts[iPart] == iPart ); // 1:1 correspondence

              if( iPart == nPartCount-1 )
                  nLineVertCount = nPointCount - panSubParts[iPart];
              else
                  nLineVertCount = panSubParts[iPart+1] - panSubParts[iPart];

              poLine->setNumPoints( nLineVertCount );

              for( i = 0; i < nLineVertCount; i++ )
              {
                  int iVert = i + panSubParts[iPart];

                  if( padfZ )
                      poLine->setPoint( i,
                                        pasPoints[iVert].x,
                                        pasPoints[iVert].y,
                                        padfZ[iVert] );
                  else
                      poLine->setPoint( i,
                                        pasPoints[iVert].x,
                                        pasPoints[iVert].y );
              }

              poMLS->addGeometryDirectly( poLine );
          }

          poGeom = poMLS;
      }
      break;

/* -------------------------------------------------------------------- */
/*      Handle polygon and multipolygon.  Each subpart is a ring.       */
/* -------------------------------------------------------------------- */
      case SG_AREA_SHAPE:
      case SG_MULTI_AREA_SHAPE:
      {
          int iPart;
          OGRMultiPolygon *poMP = NULL;

          if( nSDEGeomType == SG_MULTI_AREA_SHAPE )
              poMP = new OGRMultiPolygon();

          for( iPart = 0; iPart < nPartCount; iPart++ )
          {
              OGRPolygon *poPoly = new OGRPolygon();
              int iVert, iSubPart;
              int nNextSubPart;

              if( iPart == nPartCount-1 )
                  nNextSubPart = nSubPartCount;
              else
                  nNextSubPart = panParts[iPart+1];

              for( iSubPart = panParts[iPart]; iSubPart < nNextSubPart; iSubPart++ )
              {
                  OGRLinearRing *poRing = new OGRLinearRing();
                  int nRingVertCount;

                  if( iSubPart == nSubPartCount-1 )
                      nRingVertCount = nPointCount - panSubParts[iSubPart];
                  else
                      nRingVertCount =
                          panSubParts[iSubPart+1] - panSubParts[iSubPart];

                  poRing->setNumPoints( nRingVertCount );

                  for( iVert=0; iVert < nRingVertCount; iVert++ )
                  {
                      if( padfZ )
                          poRing->setPoint(
                              iVert,
                              pasPoints[iVert+panSubParts[iSubPart]].x,
                              pasPoints[iVert+panSubParts[iSubPart]].y,
                              padfZ[iVert+panSubParts[iSubPart]] );
                      else
                          poRing->setPoint(
                              iVert,
                              pasPoints[iVert+panSubParts[iSubPart]].x,
                              pasPoints[iVert+panSubParts[iSubPart]].y );
                  }

                  poPoly->addRingDirectly( poRing );
              }

              if( poMP )
                  poMP->addGeometryDirectly( poPoly );
              else
                  poGeom = poPoly;
          }

          if( poMP )
              poGeom = poMP;
      }
      break;

/* -------------------------------------------------------------------- */
/*      Report unsupported geometries.                                  */
/* -------------------------------------------------------------------- */
      default:
      {
          CPLError( CE_Warning, CPLE_NotSupported,
                    "Unsupported geometry type: %d",
                    (int) nSDEGeomType );
      }
    }

/* -------------------------------------------------------------------- */
/*      Cleanup                                                         */
/* -------------------------------------------------------------------- */
    CPLFree( pasPoints );
    CPLFree( panParts );
    CPLFree( panSubParts );
    CPLFree( padfZ );

    return poGeom;
}

/************************************************************************/
/*                         TranslateSDERecord()                         */
/************************************************************************/

OGRFeature *OGRSDELayer::TranslateSDERecord()

{
    unsigned int i;
    int nSDEErr;
    OGRFeature *poFeat = new OGRFeature( poFeatureDefn );

/* -------------------------------------------------------------------- */
/*      Translate field values.                                         */
/* -------------------------------------------------------------------- */
    for( i = 0; i < anFieldMap.size(); i++ )
    {
        OGRFieldDefn *poFieldDef = poFeatureDefn->GetFieldDefn( i );

        switch( anFieldTypeMap[i] )
        {
          case SE_SMALLINT_TYPE:
          {
              short   nShort;
              nSDEErr = SE_stream_get_smallint( hStream, anFieldMap[i]+1,
                                                &nShort );
              if( nSDEErr == SE_SUCCESS )
                  poFeat->SetField( i, (int) nShort );
              else if( nSDEErr != SE_NULL_VALUE )
              {
                  poDS->IssueSDEError( nSDEErr, "SE_stream_get_smallint" );
                  return NULL;
              }
          }
          break;

          case SE_INTEGER_TYPE:
          {
              LONG nValue;
              nSDEErr = SE_stream_get_integer( hStream, anFieldMap[i]+1,
                                               &nValue );
              if( nSDEErr == SE_SUCCESS )
                  poFeat->SetField( i, (int) nValue );
              else if( nSDEErr != SE_NULL_VALUE )
              {
                  poDS->IssueSDEError( nSDEErr, "SE_stream_get_integer" );
                  return NULL;
              }
          }
          break;

          case SE_FLOAT_TYPE:
          {
              float fValue;

              nSDEErr = SE_stream_get_float( hStream, anFieldMap[i]+1,
                                             &fValue );
              if( nSDEErr == SE_SUCCESS )
                  poFeat->SetField( i, (double) fValue );
              else if( nSDEErr != SE_NULL_VALUE )
              {
                  poDS->IssueSDEError( nSDEErr, "SE_stream_get_float" );
                  return NULL;
              }
          }
          break;

          case SE_DOUBLE_TYPE:
          {
              double dfValue;

              nSDEErr = SE_stream_get_double( hStream, anFieldMap[i]+1,
                                              &dfValue );
              if( nSDEErr == SE_SUCCESS )
                  poFeat->SetField( i, dfValue );
              else if( nSDEErr != SE_NULL_VALUE )
              {
                  poDS->IssueSDEError( nSDEErr, "SE_stream_get_double" );
                  return NULL;
              }
          }
          break;

          case SE_STRING_TYPE:
          {
              char *pszTempString = (char *)
                  CPLMalloc(poFieldDef->GetWidth()+1);

              nSDEErr = SE_stream_get_string( hStream, anFieldMap[i]+1,
                                              pszTempString );
              if( nSDEErr == SE_SUCCESS )
                  poFeat->SetField( i, pszTempString );
              else if( nSDEErr != SE_NULL_VALUE )
              {
                  poDS->IssueSDEError( nSDEErr, "SE_stream_get_string" );
                  return NULL;
              }
              CPLFree( pszTempString );
          }
          break;

          case SE_NSTRING_TYPE:
          {
              SE_WCHAR * pszTempStringUTF16 = (SE_WCHAR *)
                  CPLMalloc ((poFieldDef->GetWidth()+1) * sizeof(SE_WCHAR ));

              nSDEErr = SE_stream_get_nstring( hStream, anFieldMap[i]+1,
                                               pszTempStringUTF16 );

              if( nSDEErr == SE_SUCCESS )
              {
                  char* pszUTF8 = CPLRecodeFromWChar((const wchar_t*)pszTempStringUTF16, CPL_ENC_UTF16, CPL_ENC_UTF8);

                  poFeat->SetField( i, pszUTF8 );
                  CPLFree( pszUTF8 );
              }
              else if( nSDEErr != SE_NULL_VALUE )
              {
                  poDS->IssueSDEError( nSDEErr, "SE_stream_get_nstring" );
                  CPLFree( pszTempStringUTF16 );

                  return NULL;
              }
              CPLFree( pszTempStringUTF16 );
          }
          break;

#ifdef SE_UUID_TYPE
          case SE_UUID_TYPE:
          {
              char *pszTempString = (char *)
                  CPLMalloc(poFieldDef->GetWidth()+1);

              nSDEErr = SE_stream_get_uuid( hStream, anFieldMap[i]+1,
                                              pszTempString );
              if( nSDEErr == SE_SUCCESS )
                  poFeat->SetField( i, pszTempString );
              else if( nSDEErr != SE_NULL_VALUE )
              {
                  poDS->IssueSDEError( nSDEErr, "SE_stream_get_string" );
                  return NULL;
              }
              CPLFree( pszTempString );
          }
          break;
#endif
          case SE_BLOB_TYPE:
          {
              SE_BLOB_INFO sBlobVal;

              nSDEErr = SE_stream_get_blob( hStream, anFieldMap[i]+1,
                                            &sBlobVal );
              if( nSDEErr == SE_SUCCESS )
              {
                  poFeat->SetField( i,
                                    sBlobVal.blob_length,
                                    (GByte *) sBlobVal.blob_buffer );
                  SE_blob_free( &sBlobVal );
              }
              else if( nSDEErr != SE_NULL_VALUE )
              {
                  poDS->IssueSDEError( nSDEErr, "SE_stream_get_blob" );
                  return NULL;
              }
          }
          break;

#ifdef SE_CLOB_TYPE
          case SE_CLOB_TYPE:
          {
              SE_CLOB_INFO sClobVal;

              memset(&sClobVal, 0, sizeof(sClobVal)); /* to prevent from the crash in SE_stream_get_clob */
              nSDEErr = SE_stream_get_clob( hStream, anFieldMap[i]+1,
                                            &sClobVal );
              if( nSDEErr == SE_SUCCESS )
              {
                  /* the returned string is not null-terminated */
                  char* sClobstring = (char*)CPLMalloc(sizeof(char)*(sClobVal.clob_length+1));
                  memcpy(sClobstring, sClobVal.clob_buffer, sClobVal.clob_length);
                  sClobstring[sClobVal.clob_length] = '\0';

                  poFeat->SetField( i, sClobstring );
                  SE_clob_free( &sClobVal );
                  CPLFree(sClobstring);
              }
              else if( nSDEErr != SE_NULL_VALUE )
              {
                  poDS->IssueSDEError( nSDEErr, "SE_stream_get_clob" );
                  return NULL;
              }
          }
          break;

#endif

#ifdef SE_NCLOB_TYPE
          case SE_NCLOB_TYPE:
          {
              SE_NCLOB_INFO sNclobVal;

              memset(&sNclobVal, 0, sizeof(sNclobVal)); /* to prevent from the crash in SE_stream_get_nclob */
              nSDEErr = SE_stream_get_nclob( hStream, anFieldMap[i]+1,
                                            &sNclobVal );
              if( nSDEErr == SE_SUCCESS )
              {
                  /* the returned string is not null-terminated */
                  SE_WCHAR* sNclobstring = (SE_WCHAR*)CPLMalloc(sizeof(char)*(sNclobVal.nclob_length+2));
                  memcpy(sNclobstring, sNclobVal.nclob_buffer, sNclobVal.nclob_length);
                  sNclobstring[sNclobVal.nclob_length / 2] = '\0';

                  char* pszUTF8 = CPLRecodeFromWChar((const wchar_t*)sNclobstring, CPL_ENC_UTF16, CPL_ENC_UTF8);

                  poFeat->SetField( i, pszUTF8 );
                  CPLFree( pszUTF8 );

                  SE_nclob_free( &sNclobVal );
                  CPLFree(sNclobstring);
              }
              else if( nSDEErr != SE_NULL_VALUE )
              {
                  poDS->IssueSDEError( nSDEErr, "SE_stream_get_nclob" );
                  return NULL;
              }
          }
          break;

#endif

          case SE_DATE_TYPE:
          {
              struct tm sDateVal;

              nSDEErr = SE_stream_get_date( hStream, anFieldMap[i]+1,
                                            &sDateVal );
              if( nSDEErr == SE_SUCCESS )
              {
                poFeat->SetField( i, sDateVal.tm_year + 1900, sDateVal.tm_mon + 1, sDateVal.tm_mday,
                                  sDateVal.tm_hour, sDateVal.tm_min, sDateVal.tm_sec, (sDateVal.tm_isdst > 0));
              }
              else if( nSDEErr != SE_NULL_VALUE )
              {
                  poDS->IssueSDEError( nSDEErr, "SE_stream_get_date" );
                  return NULL;
              }
          }
          break;
        }
    }

/* -------------------------------------------------------------------- */
/*      Apply FID.                                                      */
/* -------------------------------------------------------------------- */
    if( iFIDColumn != -1 )
        poFeat->SetFID( poFeat->GetFieldAsInteger( iFIDColumn ) );
    else
        poFeat->SetFID( nNextFID++ );

/* -------------------------------------------------------------------- */
/*      Fetch geometry.                                                 */
/* -------------------------------------------------------------------- */
    if( iShapeColumn != -1 )
    {
        SE_SHAPE hShape = 0;

        nSDEErr = SE_shape_create( NULL, &hShape );

        if( nSDEErr != SE_SUCCESS )
            poDS->IssueSDEError( nSDEErr, "SE_shape_create" );
        else
        {
            nSDEErr = SE_stream_get_shape( hStream, (short) (iShapeColumn+1),
                                           hShape );
            if( nSDEErr != SE_SUCCESS )
                poDS->IssueSDEError( nSDEErr, "SE_stream_get_shape" );
        }

        if( nSDEErr == SE_SUCCESS )
            poFeat->SetGeometryDirectly( TranslateSDEGeometry( hShape ) );

        SE_shape_free( hShape );
    }

    return poFeat;
}

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

OGRFeature *OGRSDELayer::GetNextFeature()

{
    int nSDEErr;

/* -------------------------------------------------------------------- */
/*      Make sure we have an installed query executed.                  */
/* -------------------------------------------------------------------- */
    if( !bQueryInstalled && !InstallQuery( FALSE ) )
        return NULL;

/* -------------------------------------------------------------------- */
/*      Fetch the next record.                                          */
/* -------------------------------------------------------------------- */
    while( true )
    {
        nSDEErr = SE_stream_fetch( hStream );
        if( nSDEErr == SE_FINISHED )
        {
            bQueryInstalled = FALSE;
            return NULL;
        }
        else if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_stream_fetch" );
            return NULL;
        }

        m_nFeaturesRead++;

/* -------------------------------------------------------------------- */
/*      Translate into an OGRFeature.                                   */
/* -------------------------------------------------------------------- */
        OGRFeature *poFeature;

        poFeature = TranslateSDERecord();

        if( poFeature != NULL )
        {
            if( m_poFilterGeom == NULL
                || m_bFilterIsEnvelope
                || FilterGeometry( poFeature->GetGeometryRef() ) )
                return poFeature;

            delete poFeature;
        }
    }
}

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

OGRFeature *OGRSDELayer::GetFeature( GIntBig nFeatureId )

{
    int nSDEErr;

    if( iFIDColumn == -1 )
        return OGRLayer::GetFeature( nFeatureId );

/* -------------------------------------------------------------------- */
/*      Our direct row access will terminate any active queries.        */
/* -------------------------------------------------------------------- */
    ResetReading();

/* -------------------------------------------------------------------- */
/*      Create stream, or reset it.                                     */
/* -------------------------------------------------------------------- */
    if( ResetStream() != OGRERR_NONE )
        return NULL;

/* -------------------------------------------------------------------- */
/*      We want to fetch all the columns, just like we normally         */
/*      would for GetNextFeature().                                     */
/* -------------------------------------------------------------------- */
    nSDEErr = SE_stream_fetch_row( hStream, poFeatureDefn->GetName(),
                                   nFeatureId,
                                   CSLCount( papszAllColumns ),
                                   (const char **) papszAllColumns );
    if( nSDEErr != SE_SUCCESS )
    {
        poDS->IssueSDEError( nSDEErr, "SE_stream_fetch_row" );
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      We got our row, now translate it.                               */
/* -------------------------------------------------------------------- */
    return TranslateSDERecord();
}

/************************************************************************/
/*                          ResetStream()                               */
/*                                                                      */
/*      Create or reset stream environment                              */
/************************************************************************/

OGRErr OGRSDELayer::ResetStream()

{
    LONG                nSDEErr;

    if( hStream == NULL )
    {
        nSDEErr = SE_stream_create( poDS->GetConnection(), &hStream );
        if( nSDEErr != SE_SUCCESS) {
            poDS->IssueSDEError( nSDEErr, "SE_stream_create" );
            return OGRERR_FAILURE;
        }
        if (poDS->IsOpenForUpdate() && poDS->UseVersionEdits()) {
            nSDEErr = SE_stream_set_state(  hStream,
                                            poDS->GetNextState(),
                                            SE_NULL_STATE_ID,
                                            SE_STATE_DIFF_NOCHECK );
            if( nSDEErr != SE_SUCCESS) {
                poDS->IssueSDEError( nSDEErr, "SE_stream_set_state" );
                return OGRERR_FAILURE;
            }
        }
        else {
            nSDEErr = SE_stream_set_state(  hStream,
                                            poDS->GetState(),
                                            poDS->GetState(),
                                            SE_STATE_DIFF_NOCHECK );
            if( nSDEErr != SE_SUCCESS) {
                poDS->IssueSDEError( nSDEErr, "SE_stream_set_state" );
                return OGRERR_FAILURE;
            }
        }
    }
    else
    {
        nSDEErr = SE_stream_close( hStream, TRUE );
        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_stream_close" );
            return OGRERR_FAILURE;
        }
        if (poDS->IsOpenForUpdate() && poDS->UseVersionEdits()) {
            nSDEErr = SE_stream_set_state(  hStream,
                                            poDS->GetNextState(),
                                            SE_NULL_STATE_ID,
                                            SE_STATE_DIFF_NOCHECK );
            if( nSDEErr != SE_SUCCESS) {
                poDS->IssueSDEError( nSDEErr, "SE_stream_set_state" );
                return OGRERR_FAILURE;
            }
        }
        else {
            nSDEErr = SE_stream_set_state(  hStream,
                                            poDS->GetState(),
                                            poDS->GetState(),
                                            SE_STATE_DIFF_NOCHECK );
            if( nSDEErr != SE_SUCCESS) {
                poDS->IssueSDEError( nSDEErr, "SE_stream_set_state" );
                return OGRERR_FAILURE;
            }
        }
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                          GetFeatureCount()                           */
/*                                                                      */
/*      Issue a special "counter only" query that will just fetch       */
/*      objectids, and count the result set.  This will inherently      */
/*      include all the spatial and attribute filtering logic in the    */
/*      database.  It would be nice if we could also use a COUNT()      */
/*      operator in the database.                                       */
/************************************************************************/

GIntBig OGRSDELayer::GetFeatureCount( int bForce )

{
/* -------------------------------------------------------------------- */
/*      If there is no attribute or spatial filter in place, then       */
/*      use the SDE function call to obtain the number of               */
/*      features. The performance difference between this and manual    */
/*      iteration is significant.                                       */
/* -------------------------------------------------------------------- */
    if( osAttributeFilter.empty() && m_poFilterGeom == NULL
        && NeedLayerInfo() )
    {
        SE_LAYER_STATS layerstats = {0};
        char szTableName[SE_QUALIFIED_TABLE_NAME];
        char szShapeColumn[SE_MAX_COLUMN_LEN];
        int nSDEErr;

        szTableName[0] = '\0';
        szShapeColumn[0] = '\0';

        nSDEErr = SE_layerinfo_get_spatial_column( hLayerInfo, szTableName, szShapeColumn );
        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_layerinfo_get_spatial_column" );
            return -1;
        }

        nSDEErr = SE_layer_get_statistics( poDS->GetConnection(), szTableName, szShapeColumn,
                                           &layerstats);
        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_layer_get_statistics" );
            return -1;
        }

        return layerstats.TotalFeatures;
    }

/* -------------------------------------------------------------------- */
/*      Otherwise use direct reading of the result set, though we       */
/*      skip translating into OGRFeatures at least.                     */
/* -------------------------------------------------------------------- */
    int nSDEErr, nFeatureCount = 0;

    ResetReading();
    if( !InstallQuery( TRUE ) )
        return -1;

    for( nSDEErr = SE_stream_fetch( hStream );
         nSDEErr == SE_SUCCESS;
         nSDEErr = SE_stream_fetch( hStream ) )
    {
        nFeatureCount++;
    }

    if( nSDEErr != SE_FINISHED )
    {
        poDS->IssueSDEError( nSDEErr, "SE_stream_fetch" );
        return -1;
    }

    ResetReading();

    return nFeatureCount;
}

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

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

{
    if( !NeedLayerInfo() )
        return OGRERR_FAILURE;

    if (bForce) {
        return OGRLayer::GetExtent( psExtent, bForce );
    }

    SE_ENVELOPE  sEnvelope;
    int nSDEErr;

    nSDEErr = SE_layerinfo_get_envelope( hLayerInfo, &sEnvelope );
    if( nSDEErr != SE_SUCCESS )
    {
        poDS->IssueSDEError( nSDEErr, "SE_layerinfo_get_envelope" );
        return OGRERR_FAILURE;
    }

    psExtent->MinX = sEnvelope.minx;
    psExtent->MinY = sEnvelope.miny;
    psExtent->MaxX = sEnvelope.maxx;
    psExtent->MaxY = sEnvelope.maxy;

    return OGRERR_NONE;
}

/************************************************************************/
/*                           CreateField()                              */
/************************************************************************/
OGRErr OGRSDELayer::CreateField( OGRFieldDefn *poFieldIn, int bApproxOK )

{
    SE_COLUMN_DEF       sColumnDef;
    OGRFieldDefn        oField( poFieldIn );
    LONG                nSDEErr;

    CPLAssert( poFieldIn != NULL );
    CPLAssert( poFeatureDefn != NULL );

    /* TODO: Do we need to launder column names in the same way that OCI/PG
     * do? If so, do we also need to launder table names? */
    strncpy( sColumnDef.column_name, oField.GetNameRef(), SE_MAX_COLUMN_LEN );

    sColumnDef.nulls_allowed = TRUE;
    sColumnDef.decimal_digits = 0;

/* -------------------------------------------------------------------- */
/*      Set the new column's SDE type. We intentionally use deprecated  */
/*      SDE field types for backwards compatibility with 8.x servers    */
/* -------------------------------------------------------------------- */
    if( oField.GetType() == OFTInteger )
        sColumnDef.sde_type = SE_INTEGER_TYPE;

    else if( oField.GetType() == OFTReal )
        sColumnDef.sde_type = SE_DOUBLE_TYPE;

    else if( oField.GetType() == OFTString )
    {
        const char *pszUseNSTRING =
            CPLGetConfigOption( "OGR_SDE_USE_NSTRING", "FALSE" );

        if( bUseNSTRING || CPLTestBool( pszUseNSTRING ) )
            sColumnDef.sde_type = SE_NSTRING_TYPE;
        else
            sColumnDef.sde_type = SE_STRING_TYPE;
    }
    else if(    oField.GetType() == OFTDate
             || oField.GetType() == OFTTime
             || oField.GetType() == OFTDateTime
           )
        sColumnDef.sde_type = SE_DATE_TYPE;

    else if( bApproxOK )
    {
        CPLError( CE_Warning, CPLE_NotSupported,
                  "Can't create field %s with type %s on SDE layers - creating "
                  "as SE_STRING_TYPE.",
                  oField.GetNameRef(),
                  OGRFieldDefn::GetFieldTypeName(oField.GetType()) );

        sColumnDef.sde_type = SE_STRING_TYPE;
    }

    else
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "Can't create field %s with type %s on SDE layers.",
                  oField.GetNameRef(),
                  OGRFieldDefn::GetFieldTypeName(oField.GetType()) );

        return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Set field width and precision                                   */
/* -------------------------------------------------------------------- */
    if( bPreservePrecision && oField.GetWidth() != 0 )
    {
        sColumnDef.size = oField.GetWidth();

        if( oField.GetPrecision() != 0 && oField.GetType() == OFTReal )
            sColumnDef.decimal_digits = oField.GetPrecision();

        else if( oField.GetType() == OFTReal )
        {
            /* Float types require a >0 decimal_digits */
            sColumnDef.decimal_digits = 6;
        }
    }
    else if( !bPreservePrecision || oField.GetWidth() == 0 )
    {
        if( oField.GetType() == OFTReal )
        {
            sColumnDef.size = 24;
            sColumnDef.decimal_digits = 6;
        }
        else
        {
           /* Set the size and decimal digits to 0, which instructs SDE to use
            * DBMS-sensible defaults for these columns
            */
            sColumnDef.size = 0;
        }
    }

/* -------------------------------------------------------------------- */
/*      Create the new field                                            */
/* -------------------------------------------------------------------- */
    nSDEErr = SE_table_add_column( poDS->GetConnection(),
                                   poFeatureDefn->GetName(),
                                   &sColumnDef );
    if( nSDEErr != SE_SUCCESS )
    {
        poDS->IssueSDEError( nSDEErr, "SE_table_add_column" );
        return OGRERR_FAILURE;
    }

    poFeatureDefn->AddFieldDefn( &oField );
    anFieldTypeMap.push_back( sColumnDef.sde_type );

    return OGRERR_NONE;
}

/************************************************************************/
/*                           ISetFeature()                               */
/************************************************************************/
OGRErr OGRSDELayer::ISetFeature( OGRFeature *poFeature )

{
    LONG                nSDEErr;

    CPLAssert( poFeature != NULL );
    CPLAssert( poFeatureDefn != NULL );

    if( !NeedLayerInfo() ) // Need hCoordRef, layerinfo shape types
        return OGRERR_FAILURE;

    ResetReading();

    if( ResetStream() != OGRERR_NONE )
        return OGRERR_FAILURE;

/* -------------------------------------------------------------------- */
/*      Delegate setup of update operation to TranslateOGRRecord()      */
/* -------------------------------------------------------------------- */
    if( TranslateOGRRecord( poFeature, FALSE ) != OGRERR_NONE )
        return OGRERR_FAILURE; // TranslateOGRRecord() will report the error

/* -------------------------------------------------------------------- */
/*      Execute the update                                              */
/* -------------------------------------------------------------------- */
    nSDEErr = SE_stream_execute( hStream );
    if( nSDEErr != SE_SUCCESS )
    {
        poDS->IssueSDEError( nSDEErr, "SE_stream_execute" );
        return OGRERR_FAILURE;
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                           ICreateFeature()                            */
/************************************************************************/
OGRErr OGRSDELayer::ICreateFeature( OGRFeature *poFeature )

{
    LONG                nSDEErr;

    CPLAssert( poFeature != NULL );
    CPLAssert( poFeatureDefn != NULL );

    if( !NeedLayerInfo() ) // Need hCoordRef, layerinfo shape types
        return OGRERR_FAILURE;

    ResetReading();

    if( ResetStream() != OGRERR_NONE )
        return OGRERR_FAILURE;

/* -------------------------------------------------------------------- */
/*      Delegate setup of insert operation to TranslateOGRRecord()      */
/* -------------------------------------------------------------------- */
    if( TranslateOGRRecord( poFeature, TRUE ) != OGRERR_NONE )
        return OGRERR_FAILURE; // TranslateOGRRecord() will report the error

/* -------------------------------------------------------------------- */
/*      Execute the insert                                              */
/* -------------------------------------------------------------------- */
    nSDEErr = SE_stream_execute( hStream );
    if( nSDEErr != SE_SUCCESS )
    {
        poDS->IssueSDEError( nSDEErr, "SE_stream_execute" );
        return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      If ROWIDs are managed by SDE, then get the last inserted ID     */
/*      which is the feature ID for this new feature. If the ROWID      */
/*      is USER-managed, then TranslateOGRRecord() will have set        */
/*      the FID.                                                        */
/* -------------------------------------------------------------------- */
    if( nFIDColumnType == SE_REGISTRATION_ROW_ID_COLUMN_TYPE_SDE )
    {
        LONG            nLastFID;

        nSDEErr = SE_stream_last_inserted_row_id( hStream, &nLastFID );
        if( nSDEErr != SE_SUCCESS )
        {
            poDS->IssueSDEError( nSDEErr, "SE_stream_last_inserted_row_id" );
            return OGRERR_FAILURE;
        }

        poFeature->SetFID( nLastFID );
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                           DeleteFeature()                            */
/************************************************************************/
OGRErr OGRSDELayer::DeleteFeature( GIntBig nFID )

{
    LONG                nSDEErr;
    const char         *pszWhere;

    ResetReading();

    if( ResetStream() != OGRERR_NONE )
        return OGRERR_FAILURE;

/* -------------------------------------------------------------------- */
/*      Verify that this layer has a FID column                         */
/* -------------------------------------------------------------------- */
    if( iFIDColumn == -1 )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Layer \"%s\": cannot DeleteFeature(%ld): the layer has no "
                  "FID column detected.", poFeatureDefn->GetName(), nFID );
        return OGRERR_FAILURE;
    }

/* -------------------------------------------------------------------- */
/*      Perform the deletion                                            */
/* -------------------------------------------------------------------- */
    pszWhere = CPLSPrintf( "%s = %ld", osFIDColumnName.c_str(), nFID );

    nSDEErr = SE_stream_delete_from_table( hStream, poFeatureDefn->GetName(),
                                           pszWhere );

    if( nSDEErr == SE_NO_ROWS_DELETED )
    {
        CPLError( CE_Warning, CPLE_AppDefined,
                  "Layer \"%s\": Tried to delete a feature by FID, but no "
                  "rows were deleted!",
                  poFeatureDefn->GetName() );
    }
    else if( nSDEErr != SE_SUCCESS )
    {
        poDS->IssueSDEError( nSDEErr, "SE_stream_delete_from_table" );
        return OGRERR_FAILURE;
    }

    return OGRERR_NONE;
}

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

int OGRSDELayer::TestCapability( const char * pszCap )

{
    if( EQUAL(pszCap,OLCRandomRead) )
        return iFIDColumn != -1;

    else if( EQUAL(pszCap,OLCFastFeatureCount)
             && osAttributeFilter.empty()
             && m_poFilterGeom == NULL )
        return TRUE;

    else if( EQUAL(pszCap,OLCFastSpatialFilter) )
        return TRUE;

    else if( EQUAL(pszCap,OLCFastGetExtent) )
        return TRUE;

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

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

    else if( EQUAL(pszCap,OLCStringsAsUTF8) )
    {
        // We always treat NSTRING fields by translating to UTF8, but
        // we don't do anything to regular string fields so this is a
        // bit hard to answer simply.  Also, whether writes support UTF8
        // depend on whether the field(s) were created as NSTRING fields.
        return TRUE;
    }
    else
        return FALSE;
}
/************************************************************************/
/*                           GetSpatialRef()                            */
/************************************************************************/

OGRSpatialReference *OGRSDELayer::GetSpatialRef()

{
    NeedLayerInfo();

    return poSRS;
}
