text: /*****************************************************************************
   *
   * Project:  DB2 Spatial driver
   * Purpose:  Implements OGRDB2TableLayer class, access to an existing table.
   * Author:   David Adler, dadler at adtechgeospatial dot com
   *
   *****************************************************************************
   * Copyright (c) 2010, Tamas Szekeres
   * Copyright (c) 2015, David Adler
   * 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 "cpl_conv.h"
  #include "ogr_db2.h"
  
  /************************************************************************/
  /*                         OGRDB2AppendEscaped( )                     */
  /************************************************************************/
  
  void OGRDB2AppendEscaped( OGRDB2Statement* poStatement,
                            const char* pszStrValue)
  {
      if (!pszStrValue)
          poStatement->Append("null");
  
      size_t  iIn, iOut , nTextLen = strlen(pszStrValue);
      char    *pszEscapedText = (char *) VSIMalloc(nTextLen*2 + 3);
  
      pszEscapedText[0] = '\'';
  
      for( iIn = 0, iOut = 1; iIn < nTextLen; iIn++ )
      {
          switch( pszStrValue[iIn] )
          {
          case '\'':
              pszEscapedText[iOut++] = '\''; // double quote
              pszEscapedText[iOut++] = pszStrValue[iIn];
              break;
  
          default:
              pszEscapedText[iOut++] = pszStrValue[iIn];
              break;
          }
      }
  
      pszEscapedText[iOut++] = '\'';
  
      pszEscapedText[iOut] = '\0';
  
      poStatement->Append(pszEscapedText);
  
      CPLFree( pszEscapedText );
  }
  
  /************************************************************************/
  /*                          OGRDB2TableLayer()                 */
  /************************************************************************/
  
  OGRDB2TableLayer::OGRDB2TableLayer( OGRDB2DataSource *poDSIn )
  
  {
      poDS = poDSIn;
      m_poStmt = NULL;
      m_poPrepStmt = NULL;
      m_pszQuery = NULL;
      m_nFeaturesRead = 0;
  
      bUpdateAccess = TRUE;
  
      iNextShapeId = 0;
  
      nSRSId = -1;
  
      poFeatureDefn = NULL;
  
      pszTableName = NULL;
      m_pszLayerName = NULL;
      pszSchemaName = NULL;
      pszFIDColumn = NULL;
      eGeomType = wkbNone;
  }
  
  /************************************************************************/
  /*                          ~OGRDB2TableLayer()                */
  /************************************************************************/
  
  OGRDB2TableLayer::~OGRDB2TableLayer()
  
  {
      CPLDebug("OGRDB2TableLayer::~OGRDB2TableLayer","entering");
      CPLFree( pszTableName );
      CPLFree( m_pszLayerName );
      CPLFree( pszSchemaName );
  
      CPLFree( m_pszQuery );
      ClearStatement();
      if( m_poPrepStmt != NULL )
      {
          delete m_poPrepStmt;
          m_poPrepStmt = NULL;
      }
      CPLDebug("OGRDB2TableLayer::~OGRDB2TableLayer","exiting");
  }
  
  /************************************************************************/
  /*                               GetName()                              */
  /************************************************************************/
  
  const char *OGRDB2TableLayer::GetName()
  
  {
      return m_pszLayerName;
  }
  
  /************************************************************************/
  /*                             GetLayerDefn()                           */
  /************************************************************************/
  OGRFeatureDefn* OGRDB2TableLayer::GetLayerDefn()
  {
      if (poFeatureDefn)
          return poFeatureDefn;
  
      OGRDB2Session *poSession = poDS->GetSession();
      /* -------------------------------------------------------------------- */
      /*      Do we have a simple primary key?                                */
      /* -------------------------------------------------------------------- */
      OGRDB2Statement oGetKey( poSession );
      CPLDebug( "OGR_DB2TableLayer::GetLayerDefn",
                "pszTableName: %s; pszSchemaName: %s",
                pszTableName, pszSchemaName);
      if( oGetKey.GetPrimaryKeys( pszTableName, NULL, pszSchemaName ) ) {
          if( oGetKey.Fetch() )
          {
              pszFIDColumn = CPLStrdup(oGetKey.GetColData( 3 ));
              if( oGetKey.Fetch() ) // more than one field in key!
              {
                  oGetKey.Clear();
                  CPLFree( pszFIDColumn );
                  pszFIDColumn = NULL;
                  CPLDebug( "OGR_DB2TableLayer::GetLayerDefn",
                            "Table %s has multiple primary key fields, "
                            "ignoring them all.", pszTableName );
              } else {
                  // Attempt to get the 'identity' and 'generated' information
                  // from syscat.columns. This is only valid on DB2 LUW so if it
                  // fails, we assume that we are running on z/OS.
                  OGRDB2Statement oStatement = OGRDB2Statement(
                                                   poDS->GetSession());
                  oStatement.Appendf( "select identity, generated "
                                      "from syscat.columns "
                                      "where tabschema = '%s' "
                                      "and tabname = '%s' and colname = '%s'",
                                      pszSchemaName, pszTableName,
                                      pszFIDColumn );
  
                  if( oStatement.DB2Execute("OGR_DB2TableLayer::GetLayerDefn") )
                  {
                      if( oStatement.Fetch() )
                      {
                          if ( oStatement.GetColData( 0 )
                                  && EQUAL(oStatement.GetColData( 0 ), "Y")) {
                              bIsIdentityFid = TRUE;
                              if ( oStatement.GetColData( 1 ) ) {
                                  cGenerated = oStatement.GetColData( 1 )[0];
                              }
                          }
                      }
                  } else {
                      CPLDebug( "OGR_DB2TableLayer::GetLayerDefn",
                                "Must be z/OS");
                      // on z/OS, get all the column data for table and loop
                      // through looking for the FID column, then check the
                      // column default information for 'IDENTIY' and 'ALWAYS'
                      if (oGetKey.GetColumns(pszTableName, NULL, pszSchemaName))
                      {
                          CPLDebug( "OGR_DB2TableLayer::GetLayerDefn",
                                    "GetColumns succeeded");
                          CPLDebug( "OGR_DB2TableLayer::GetLayerDefn",
                                    "ColName[0]: '%s'",oGetKey.GetColName(0));
                          for (int idx = 0; idx < oGetKey.GetColCount(); idx++)
                          {
                              CPLDebug( "OGR_DB2TableLayer::GetLayerDefn",
                                        "ColName[0]: '%s'",
                                        oGetKey.GetColName(idx));
                              if (!strcmp(pszFIDColumn,oGetKey.GetColName(idx)))
                              {
                                  CPLDebug( "OGR_DB2TableLayer::GetLayerDefn",
                                            "ColDef[0]: '%s'",
                                            oGetKey.GetColColumnDef(idx));
                                  if (strstr(oGetKey.GetColColumnDef(idx),
                                             "IDENTITY"))
                                      bIsIdentityFid = TRUE;
                                  if (strstr(oGetKey.GetColColumnDef(idx),
                                             "ALWAYS"))
                                      cGenerated = 'A';
                              }
                          }
                      }
                  }
                  CPLDebug( "OGR_DB2TableLayer::GetLayerDefn",
                            "FIDColumn: '%s', identity: '%d', generated: '%c'",
                            pszFIDColumn, bIsIdentityFid, cGenerated);
              }
          }
      } else
          CPLDebug( "OGR_DB2TableLayer::GetLayerDefn", "GetPrimaryKeys failed");
  
      /* -------------------------------------------------------------------- */
      /*      Get the column definitions for this table.                      */
      /* -------------------------------------------------------------------- */
      OGRDB2Statement oGetCol( poSession );
      CPLErr eErr;
  
      if( !oGetCol.GetColumns( pszTableName, "", pszSchemaName ) )
          return NULL;
  
      eErr = BuildFeatureDefn( m_pszLayerName, &oGetCol );
      if( eErr != CE_None )
          return NULL;
  
      if (eGeomType != wkbNone)
          poFeatureDefn->SetGeomType(eGeomType);
  
      if ( GetSpatialRef() && poFeatureDefn->GetGeomFieldCount() == 1)
          poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef( poSRS );
  
      if( poFeatureDefn->GetFieldCount() == 0 &&
              pszFIDColumn == NULL && pszGeomColumn == NULL )
      {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "No column definitions found for table '%s', "
                    "layer not usable.",
                    m_pszLayerName );
          return NULL;
      }
  
      /* -------------------------------------------------------------------- */
      /*      If we got a geometry column, does it exist?  Is it binary?      */
      /* -------------------------------------------------------------------- */
  
      if( pszGeomColumn != NULL )
      {
          poFeatureDefn->GetGeomFieldDefn(0)->SetName( pszGeomColumn );
          int iColumn = oGetCol.GetColId( pszGeomColumn );
          if( iColumn < 0 )
          {
              CPLError( CE_Failure, CPLE_AppDefined,
                        "Column %s requested for geometry, "
                        "but it does not exist.",
                        pszGeomColumn );
              CPLFree( pszGeomColumn );
              pszGeomColumn = NULL;
          }
      }
  
      return poFeatureDefn;
  }
  
  /************************************************************************/
  /*                             Initialize()                             */
  /************************************************************************/
  
  CPLErr OGRDB2TableLayer::Initialize( const char *pszSchema,
                                       const char *pszLayerName,
                                       const char *pszGeomCol,
                                       CPL_UNUSED int nCoordDimension,
                                       int nSRId,
                                       const char *pszSRText,
                                       OGRwkbGeometryType eType )
  {
  //    CPLFree( pszFIDColumn );
      pszFIDColumn = NULL;
  
      CPLDebug( "OGR_DB2TableLayer::Initialize",
                "schema: '%s', layerName: '%s', geomCol: '%s'",
                pszSchema, pszLayerName, pszGeomCol);
      CPLDebug( "OGR_DB2TableLayer::Initialize",
                "nSRId: '%d', eType: '%d', srText: '%s'",
                nSRId, eType, pszSRText);
  
  
      /* -------------------------------------------------------------------- */
      /*      Parse out schema name if present in layer.  We assume a         */
      /*      schema is provided if there is a dot in the name, and that      */
      /*      it is in the form <schema>.<tablename>                          */
      /* -------------------------------------------------------------------- */
      const char *pszDot = strstr(pszLayerName,".");
      if( pszDot != NULL )
      {
          pszTableName = CPLStrdup(pszDot + 1);
          pszSchemaName = CPLStrdup(pszLayerName);
          pszSchemaName[pszDot - pszLayerName] = '\0';
          this->m_pszLayerName = CPLStrdup(pszLayerName);
      }
      else
      {
          pszTableName = CPLStrdup(pszLayerName);
          pszSchemaName = CPLStrdup(pszSchema);
          this->m_pszLayerName = CPLStrdup(CPLSPrintf("%s.%s", pszSchemaName,
                                           pszTableName));
      }
      SetDescription( this->m_pszLayerName );
      CPLDebug( "OGR_DB2TableLayer::Initialize",
                "this->m_pszLayerName: '%s', layerName: '%s', geomCol: '%s'",
                this->m_pszLayerName, pszLayerName, pszGeomCol);
      /* -------------------------------------------------------------------- */
      /*      Have we been provided a geometry column?                        */
      /* -------------------------------------------------------------------- */
  //    CPLFree( pszGeomColumn ); LATER
      if( pszGeomCol == NULL )
          GetLayerDefn(); /* fetch geom column if not specified */
      else
          pszGeomColumn = CPLStrdup( pszGeomCol );
  
      if (eType != wkbNone)
          eGeomType = eType;
  
      /* -------------------------------------------------------------------- */
      /*             Try to find out the spatial reference                    */
      /* -------------------------------------------------------------------- */
  
      nSRSId = nSRId;
  
      if (pszSRText)
      {
          /* Process srtext directly if specified */
          poSRS = new OGRSpatialReference();
          if( poSRS->importFromWkt( (char**)&pszSRText ) != OGRERR_NONE )
          {
              delete poSRS;
              poSRS = NULL;
          }
      }
  
      if (!poSRS)
      {
          if (nSRSId < 0)
              nSRSId = FetchSRSId();
  
          GetSpatialRef();
      }
  
      return CE_None;
  }
  
  /************************************************************************/
  /*                         FetchSRSId()                                 */
  /************************************************************************/
  
  int OGRDB2TableLayer::FetchSRSId()
  {
      OGRDB2Statement oStatement = OGRDB2Statement( poDS->GetSession() );
  
  // first try to get the srid from st_geometry_columns
  // if the spatial column was registered
      oStatement.Appendf( "select srs_id from db2gse.st_geometry_columns "
                          "where table_schema = '%s' and table_name = '%s'",
                          pszSchemaName, pszTableName );
  
      if( oStatement.DB2Execute("OGRDB2TableLayer::FetchSRSId")
              && oStatement.Fetch() )
      {
          if ( oStatement.GetColData( 0 ) )
              nSRSId = atoi( oStatement.GetColData( 0 ) );
      }
  
  // If it was not found there, try to get it from the data table.
  // This only works if there is spatial data in the first row.
      if (nSRSId < 0 )
      {
          oStatement.Clear();
          oStatement.Appendf("select db2gse.st_srid(%s) from %s.%s "
                             "fetch first row only",
                             pszGeomColumn, pszSchemaName, pszTableName);
          if ( oStatement.DB2Execute("OGR_DB2TableLayer::FetchSRSId")
                  && oStatement.Fetch() )
          {
              if ( oStatement.GetColData( 0 ) )
                  nSRSId = atoi( oStatement.GetColData( 0 ) );
          }
      }
      CPLDebug( "OGR_DB2TableLayer::FetchSRSId", "nSRSId: '%d'",
                nSRSId);
      return nSRSId;
  }
  
  /************************************************************************/
  /*                       CreateSpatialIndex()                           */
  /*                                                                      */
  /*      Create a spatial index on the geometry column of the layer      */
  /************************************************************************/
  
  OGRErr OGRDB2TableLayer::CreateSpatialIndex()
  {
      CPLDebug("OGRDB2TableLayer::CreateSpatialIndex","Enter");
      if (poDS->m_bIsZ) {
          CPLDebug("OGRDB2TableLayer::CreateSpatialIndex",
                   "Don't create spatial index on z/OS");
          return OGRERR_NONE;
      }
      GetLayerDefn();
  
      OGRDB2Statement oStatement( poDS->GetSession() );
  
  
      OGREnvelope oExt;
      if (GetExtent(&oExt, TRUE) != OGRERR_NONE)
      {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "Failed to get extent for spatial index." );
          return OGRERR_FAILURE;
      }
      CPLDebug("OGRDB2TableLayer::CreateSpatialIndex",
               "BOUNDING_BOX =(%.15g, %.15g, %.15g, %.15g)",
               oExt.MinX, oExt.MinY, oExt.MaxX, oExt.MaxY );
  
      oStatement.Appendf("CREATE  INDEX %s.%s_sidx ON %s.%s ( %s ) "
                         "extend using db2gse.spatial_index(.1,0.5,0)",
                         pszSchemaName, pszTableName,
                         pszSchemaName, pszTableName, pszGeomColumn );
  
      if( !oStatement.DB2Execute("OGR_DB2TableLayer::CreateSpatialIndex") )
      {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "Failed to create the spatial index, %s.",
                    poDS->GetSession()->GetLastError());
          return OGRERR_FAILURE;
      }
  
      return OGRERR_NONE;
  }
  
  /************************************************************************/
  /*                       DropSpatialIndex()                             */
  /*                                                                      */
  /*      Drop the spatial index on the geometry column of the layer      */
  /************************************************************************/
  
  void OGRDB2TableLayer::DropSpatialIndex()
  {
      GetLayerDefn();
  
      OGRDB2Statement oStatement( poDS->GetSession() );
  
      oStatement.Appendf("DROP INDEX %s.%s",
                         pszSchemaName, pszTableName);
  
      //poDS->GetSession()->BeginTransaction();
  
      if( !oStatement.DB2Execute("OGR_DB2TableLayer::DropSpatialIndex") )
      {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "Failed to drop the spatial index, %s.",
                    poDS->GetSession()->GetLastError());
          return;
      }
  
      //poDS->GetSession()->CommitTransaction();
  }
  
  /************************************************************************/
  /*                            BuildFields()                             */
  /*                                                                      */
  /*      Build list of fields to fetch, performing any required          */
  /*      transformations (such as on geometry).                          */
  /************************************************************************/
  
  CPLString OGRDB2TableLayer::BuildFields()
  
  {
      int i = 0;
      int nColumn = 0;
      CPLString osFieldList;
  
      GetLayerDefn();
  
      if( pszFIDColumn && poFeatureDefn->GetFieldIndex( pszFIDColumn ) == -1 )
      {
          /* Always get the FID column */
          osFieldList += " ";
          osFieldList += pszFIDColumn;
          osFieldList += " ";
          ++nColumn;
      }
  
      if( pszGeomColumn && !poFeatureDefn->IsGeometryIgnored())
      {
          if( nColumn > 0 )
              osFieldList += ", ";
  
          osFieldList += " db2gse.st_astext(";
          osFieldList += pszGeomColumn;
          osFieldList += ") as ";
          osFieldList += pszGeomColumn;
  
          osFieldList += " ";
  
          ++nColumn;
      }
  
      if (poFeatureDefn->GetFieldCount() > 0)
      {
          /* need to reconstruct the field ordinals list */
          CPLFree(panFieldOrdinals);
          panFieldOrdinals = (int *) CPLMalloc( sizeof(int)
                                      * poFeatureDefn->GetFieldCount() );
  
          for( i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
          {
              if ( poFeatureDefn->GetFieldDefn(i)->IsIgnored() )
                  continue;
  
              const char *pszName =poFeatureDefn->GetFieldDefn(i)->GetNameRef();
  
              if( nColumn > 0 )
                  osFieldList += ", ";
  
              osFieldList += " ";
              osFieldList += pszName;
              osFieldList += " ";
  
              panFieldOrdinals[i] = nColumn;
  
              ++nColumn;
          }
      }
  
      return osFieldList;
  }
  
  /************************************************************************/
  /*                           ClearStatement()                           */
  /************************************************************************/
  
  void OGRDB2TableLayer::ClearStatement()
  
  {
      if( m_poStmt != NULL )
      {
          delete m_poStmt;
          m_poStmt = NULL;
      }
  }
  
  /************************************************************************/
  /*                            GetStatement()                            */
  /************************************************************************/
  
  OGRDB2Statement *OGRDB2TableLayer::GetStatement()
  
  {
      if( m_poStmt == NULL )
      {
          m_poStmt = BuildStatement(BuildFields());
          iNextShapeId = 0;
      }
  
      return m_poStmt;
  }
  
  
  /************************************************************************/
  /*                           BuildStatement()                           */
  /************************************************************************/
  
  OGRDB2Statement* OGRDB2TableLayer::BuildStatement(const char* pszColumns)
  
  {
      OGRDB2Statement* poStatement = new OGRDB2Statement( poDS->GetSession());
      poStatement->Append( "select " );
      poStatement->Append( pszColumns );
      poStatement->Append( " from " );
      poStatement->Append( pszSchemaName );
      poStatement->Append( "." );
      poStatement->Append( pszTableName );
  
      /* Append attribute query if we have it */
      if( m_pszQuery != NULL )
          poStatement->Appendf( " where (%s)", m_pszQuery );
  
      /* If we have a spatial filter, query on it */
      if ( m_poFilterGeom != NULL )
      {
          if( m_pszQuery == NULL )
              poStatement->Append( " where" );
          else
              poStatement->Append( " and" );
  
          poStatement->Appendf(" db2gse.envelopesintersect(%s,%.15g,%.15g,"
                               "%.15g,%.15g, 0) = 1",
                               pszGeomColumn, m_sFilterEnvelope.MinX,
                               m_sFilterEnvelope.MinY, m_sFilterEnvelope.MaxX,
                               m_sFilterEnvelope.MaxY );
      }
  
      if( poStatement->DB2Execute("OGR_DB2TableLayer::BuildStatement") ) {
          return poStatement;
      }
      else
      {
          delete poStatement;
          CPLDebug( "OGR_DB2TableLayer::BuildStatement", "ExecuteSQL Failed" );
          return NULL;
      }
  }
  
  /************************************************************************/
  /*                            ResetReading()                            */
  /************************************************************************/
  
  void OGRDB2TableLayer::ResetReading()
  
  {
      ClearStatement();
      OGRDB2Layer::ResetReading();
  }
  
  /************************************************************************/
  /*                             GetFeature()                             */
  /************************************************************************/
  
  OGRFeature *OGRDB2TableLayer::GetFeature( GIntBig nFeatureId )
  
  {
  
      if( pszFIDColumn == NULL )
          return OGRDB2Layer::GetFeature( nFeatureId );
  
      ClearStatement();
  
      iNextShapeId = nFeatureId;
  
      m_poStmt = new OGRDB2Statement( poDS->GetSession() );
      CPLString osFields = BuildFields();
      m_poStmt->Appendf( "select %s from %s where %s = %ld", osFields.c_str(),
                         poFeatureDefn->GetName(), pszFIDColumn, nFeatureId );
  
      if( !m_poStmt->DB2Execute("OGR_DB2TableLayer::GetFeature") )
      {
          delete m_poStmt;
          m_poStmt = NULL;
          return NULL;
      }
  
      return GetNextRawFeature();
  }
  
  /************************************************************************/
  /*                         SetAttributeFilter()                         */
  /************************************************************************/
  
  OGRErr OGRDB2TableLayer::SetAttributeFilter( const char *pszQuery )
  
  {
      CPLFree(m_pszAttrQueryString);
      m_pszAttrQueryString = (pszQuery) ? CPLStrdup(pszQuery) : NULL;
  
      if( (pszQuery == NULL && this->m_pszQuery == NULL)
              || (pszQuery != NULL && this->m_pszQuery != NULL
                  && EQUAL(pszQuery,this->m_pszQuery)) )
          return OGRERR_NONE;
  
      CPLFree( this->m_pszQuery );
      this->m_pszQuery = (pszQuery) ? CPLStrdup( pszQuery ) : NULL;
  
      ClearStatement();
  
      return OGRERR_NONE;
  }
  
  
  /************************************************************************/
  /*                           TestCapability()                           */
  /************************************************************************/
  
  int OGRDB2TableLayer::TestCapability( const char * pszCap )
  
  {
      if ( bUpdateAccess )
      {
          if( EQUAL(pszCap,OLCSequentialWrite) || EQUAL(pszCap,OLCCreateField)
                  || EQUAL(pszCap,OLCDeleteFeature) )
              return TRUE;
  
          else if( EQUAL(pszCap,OLCRandomWrite) )
              return (pszFIDColumn != NULL);
      }
  
      if( EQUAL(pszCap,OLCTransactions) )
          return TRUE;
  
      if( EQUAL(pszCap,OLCIgnoreFields) )
          return TRUE;
  
      if( EQUAL(pszCap,OLCRandomRead) )
          return (pszFIDColumn != NULL);
      else if( EQUAL(pszCap,OLCFastFeatureCount) )
          return TRUE;
      else
          return OGRDB2Layer::TestCapability( pszCap );
  }
  
  /************************************************************************/
  /*                          GetFeatureCount()                           */
  /************************************************************************/
  
  GIntBig OGRDB2TableLayer::GetFeatureCount( int bForce )
  
  {
      GetLayerDefn();
  
      if( TestCapability(OLCFastFeatureCount) == FALSE )
          return OGRDB2Layer::GetFeatureCount( bForce );
  
      ClearStatement();
  
      OGRDB2Statement* poStatement = BuildStatement( "count(*)" );
  
      if (poStatement == NULL || !poStatement->Fetch())
      {
          delete poStatement;
          return OGRDB2Layer::GetFeatureCount( bForce );
      }
  
      int nRet = atoi(poStatement->GetColData( 0 ));
      delete poStatement;
      return nRet;
  }
  
  
  /************************************************************************/
  /*                            CreateField()                             */
  /************************************************************************/
  
  OGRErr OGRDB2TableLayer::CreateField( OGRFieldDefn *poFieldIn,
                                        int bApproxOK )
  
  {
      char                szFieldType[256];
      OGRFieldDefn        oField( poFieldIn );
  
      GetLayerDefn();
  
      /* -------------------------------------------------------------------- */
      /*      Do we want to "launder" the column names into DB2               */
      /*      friendly format?                                                */
      /* -------------------------------------------------------------------- */
      if( bLaunderColumnNames )
      {
          char    *pszSafeName = poDS->LaunderName( oField.GetNameRef() );
  
          oField.SetName( pszSafeName );
          CPLFree( pszSafeName );
      }
  
      /* -------------------------------------------------------------------- */
      /*      Identify the DB2 type.                                          */
      /* -------------------------------------------------------------------- */
  
      int fieldType = oField.GetType();
      CPLDebug("OGR_DB2TableLayer::CreateField","fieldType: %d", fieldType);
      if( oField.GetType() == OFTInteger )
      {
          if( oField.GetWidth() > 0 && bPreservePrecision )
              sprintf( szFieldType, "numeric(%d,0)", oField.GetWidth() );
          else
              strcpy( szFieldType, "int" );
      }
      else if( oField.GetType() == OFTInteger64 )
      {
          if( oField.GetWidth() > 0 && bPreservePrecision )
              sprintf( szFieldType, "numeric(%d,0)", oField.GetWidth() );
          else
              strcpy( szFieldType, "bigint" );
      }
      else if( oField.GetType() == OFTReal )
      {
          if( oField.GetWidth() > 0 && oField.GetPrecision() > 0
                  && bPreservePrecision )
              sprintf( szFieldType, "numeric(%d,%d)",
                       oField.GetWidth(), oField.GetPrecision() );
          else
              strcpy( szFieldType, "float" );
      }
      else if( oField.GetType() == OFTString )
      {
          if( oField.GetWidth() == 0 || !bPreservePrecision )
              strcpy( szFieldType, "varchar(MAX)" );
          else
              sprintf( szFieldType, "varchar(%d)", oField.GetWidth() );
      }
      else if( oField.GetType() == OFTDate )
      {
          strcpy( szFieldType, "date" );
      }
      else if( oField.GetType() == OFTTime )
      {
          strcpy( szFieldType, "time(7)" );
      }
      else if( oField.GetType() == OFTDateTime )
      {
          strcpy( szFieldType, "datetime" );
      }
      else if( oField.GetType() == OFTBinary )
      {
          strcpy( szFieldType, "image" );
      }
      else if( bApproxOK )
      {
          CPLError( CE_Warning, CPLE_NotSupported,
                    "Can't create field %s with type %s on DB2 layers.  "
                    "Creating as varchar.",
                    oField.GetNameRef(),
                    OGRFieldDefn::GetFieldTypeName(oField.GetType()) );
          strcpy( szFieldType, "varchar" );
      }
      else
      {
          CPLError( CE_Failure, CPLE_NotSupported,
                    "Can't create field %s with type %s on DB2 layers.",
                    oField.GetNameRef(),
                    OGRFieldDefn::GetFieldTypeName(oField.GetType()) );
  
          return OGRERR_FAILURE;
      }
  
      /* -------------------------------------------------------------------- */
      /*      Create the new field.                                           */
      /* -------------------------------------------------------------------- */
  
      OGRDB2Statement oStmt( poDS->GetSession() );
  
      oStmt.Appendf( "ALTER TABLE %s.%s ADD COLUMN %s %s",
                     pszSchemaName, pszTableName, oField.GetNameRef(),
                     szFieldType);
  
      if( !oStmt.DB2Execute("OGR_DB2TableLayer::CreateField") )
      {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "Error creating field %s, %s", oField.GetNameRef(),
                    poDS->GetSession()->GetLastError() );
  
          return OGRERR_FAILURE;
      }
  
      /* -------------------------------------------------------------------- */
      /*      Add the field to the OGRFeatureDefn.                            */
      /* -------------------------------------------------------------------- */
  
      poFeatureDefn->AddFieldDefn( &oField );
      return OGRERR_NONE;
  }
  
  /************************************************************************/
  /*                            ISetFeature()                             */
  /*                                                                      */
  /*     ISetFeature() is implemented by an UPDATE SQL command            */
  /************************************************************************/
  
  OGRErr OGRDB2TableLayer::ISetFeature( OGRFeature *poFeature )
  
  {
      OGRErr              eErr = OGRERR_FAILURE;
  
      GetLayerDefn();
  
      if( NULL == poFeature )
      {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "NULL pointer to OGRFeature passed to SetFeature()." );
          return eErr;
      }
  
      if( poFeature->GetFID() == OGRNullFID )
      {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "FID required on features given to SetFeature()." );
          return eErr;
      }
  
      if( !pszFIDColumn )
      {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "Unable to update features in tables without\n"
                    "a recognised FID column.");
          return eErr;
  
      }
  
      ClearStatement();
  
      /* -------------------------------------------------------------------- */
      /*      Form the UPDATE command.                                        */
      /* -------------------------------------------------------------------- */
      if (PrepareFeature(poFeature, 'U'))
          return OGRERR_FAILURE;
  
  
      int nFieldCount = poFeatureDefn->GetFieldCount();
      int nBindNum = 0;
      void** papBindBuffer = (void**)CPLMalloc(sizeof(void*) * nFieldCount);
  
      /* Set the geometry */
      OGRGeometry *poGeom = poFeature->GetGeometryRef();
      char    *pszWKT = NULL;
  
      if (poGeom != NULL && pszGeomColumn != NULL)
      {
          if( poGeom->exportToWkt( &pszWKT ) == OGRERR_NONE)
          {
              int nLen = (int) strlen(pszWKT);
              if (m_poPrepStmt->DB2BindParameterIn(
                          "OGRDB2TableLayer::UpdateFeature",
                          (nBindNum + 1),
                          SQL_C_CHAR,
                          SQL_LONGVARCHAR,
                          nLen,
                          (void *)(pszWKT)))
              {
                  papBindBuffer[nBindNum] = pszWKT;
                  nBindNum++;
              } else {
                  CPLDebug("OGRDB2TableLayer::UpdateFeature",
                           "Bind parameter failed");
                  FreeBindBuffer(nBindNum, papBindBuffer);
                  return OGRERR_FAILURE;
              }
          }
      }
  
      for( int i = 0; i < nFieldCount; i++ )
      {
  //        int nOGRFieldType = poFeatureDefn->GetFieldDefn(i)->GetType();
  //        CPLDebug("OGRDB2TableLayer::UpdateFeature",
  //               "i: %d; nOGRFieldType: %d",
  //                i, nOGRFieldType);
  
          if (BindFieldValue(m_poPrepStmt,
                             poFeature, i,
                             nBindNum, papBindBuffer) != OGRERR_NONE) {
              CPLDebug("OGRDB2TableLayer::UpdateFeature",
                       "Bind parameter failed");
              FreeBindBuffer(nBindNum, papBindBuffer);
              return OGRERR_FAILURE;
          }
          nBindNum++;
      }
  
  
      /* -------------------------------------------------------------------- */
      /*      Execute the update.                                             */
      /* -------------------------------------------------------------------- */
      if( !m_poPrepStmt->DB2Execute("OGR_DB2TableLayer::UpdateFeature") )
      {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "Error updating feature with FID:" CPL_FRMT_GIB ", %s",
                    poFeature->GetFID(),
                    poDS->GetSession()->GetLastError() );
  
          return OGRERR_FAILURE;
      }
  
      FreeBindBuffer(nBindNum, papBindBuffer);
  
      return OGRERR_NONE;
  }
  
  /************************************************************************/
  /*                          DeleteFeature()                             */
  /************************************************************************/
  
  OGRErr OGRDB2TableLayer::DeleteFeature( GIntBig nFID )
  
  {
      CPLDebug("OGR_DB2TableLayer::DeleteFeature",
               " entering, nFID: " CPL_FRMT_GIB,nFID);
      GetLayerDefn();
  
      if( pszFIDColumn == NULL )
      {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "DeleteFeature() without any FID column." );
          return OGRERR_FAILURE;
      }
  
      if( nFID == OGRNullFID )
      {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "DeleteFeature() with unset FID fails." );
          return OGRERR_FAILURE;
      }
  
      ClearStatement();
  
      /* -------------------------------------------------------------------- */
      /*      Drop the record with this FID.                                  */
      /* -------------------------------------------------------------------- */
      OGRDB2Statement oStatement( poDS->GetSession() );
  
      oStatement.Appendf("DELETE FROM %s WHERE %s = " CPL_FRMT_GIB,
                         poFeatureDefn->GetName(), pszFIDColumn, nFID);
      if( !oStatement.DB2Execute("OGR_DB2TableLayer::DeleteFeature") )
      {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "Attempt to delete feature with FID " CPL_FRMT_GIB
                    " failed. %s",
                    nFID, poDS->GetSession()->GetLastError() );
          return OGRERR_FAILURE;
      }
      return OGRERR_NONE;
  }
  
  
  
  
  
  
  /************************************************************************/
  /*                          isFieldTypeSupported()                      */
  /************************************************************************/
  
  OGRErr OGRDB2TableLayer::isFieldTypeSupported( OGRFieldType nFieldType )
  
  {
      switch(nFieldType) {
      case OFTInteger:
      case OFTReal:
      case OFTString:
      case OFTDateTime:
      case OFTInteger64:
          return OGRERR_NONE;
      default:
          return OGRERR_FAILURE;
      }
  }
  
  /************************************************************************/
  /*                          PrepareFeature()                            */
  /************************************************************************/
  
  OGRErr OGRDB2TableLayer::PrepareFeature( OGRFeature *poFeature, char cType )
  
  {
  // LATER - this defeats the point of prepared statements but need to find
  // some place to clean up to avoid reusing the wrong statement
      if (m_poPrepStmt) delete m_poPrepStmt;
      m_poPrepStmt =  new OGRDB2Statement( poDS->GetSession());
  
      char    *pszWKT = NULL;
      CPLString osValues= " VALUES(";
      int nFieldCount = poFeatureDefn->GetFieldCount();
  
      if (cType == 'I')
          m_poPrepStmt->Appendf( "INSERT INTO %s.%s (",
                                 pszSchemaName, pszTableName );
      else
          m_poPrepStmt->Appendf( "UPDATE %s.%s SET ",
                                 pszSchemaName, pszTableName);
      int bNeedComma = FALSE;
      OGRGeometry *poGeom = poFeature->GetGeometryRef();
  
      if (poGeom != NULL && pszGeomColumn != NULL)
      {
          if( poGeom->exportToWkt( &pszWKT ) == OGRERR_NONE)
          {
              int nLen = (int) strlen(pszWKT);
              if (cType == 'I')
              {
                  m_poPrepStmt->Append( pszGeomColumn );
                  CPLString geomValue;
                  geomValue.Printf( "DB2GSE.ST_%s(CAST( ? AS CLOB(2M)),%d)",
                                    poGeom->getGeometryName(), nSRSId );
                  osValues.append(geomValue);
              } else {
                  m_poPrepStmt->Appendf( "%s = "
                                      "DB2GSE.ST_%s(CAST( ? AS CLOB(%d)),%d)",
                                      pszGeomColumn, poGeom->getGeometryName(),
                                      nLen, nSRSId );
              }
              bNeedComma = TRUE;
          }
      }
  
  // Explicitly add FID column and value if needed
      if( cType == 'I' && poFeature->GetFID() != OGRNullFID
              && pszFIDColumn != NULL && cGenerated != 'A' )
      {
          if (bNeedComma)
          {
              m_poPrepStmt->Appendf( ", ");
              osValues.append(", ");
          }
          m_poPrepStmt->Appendf( "%s", pszFIDColumn );
          osValues.append("?");
          bNeedComma = TRUE;
      }
  
      for( int i = 0; i < nFieldCount; i++ )
      {
  
          if( !poFeature->IsFieldSet( i ) )
              continue;
  
          if (bNeedComma)
          {
              m_poPrepStmt->Appendf( ", ");
              osValues.append(", ");
          }
          bNeedComma = TRUE;
          if (cType == 'I') {
              m_poPrepStmt->Appendf( "%s",
                                  poFeatureDefn->GetFieldDefn(i)->GetNameRef());
              osValues.append("?");
          } else {
              m_poPrepStmt->Appendf( "%s = ?",
                                  poFeatureDefn->GetFieldDefn(i)->GetNameRef());
          }
      }
      if (cType == 'I') {
          m_poPrepStmt->Appendf( ") %s )", osValues.c_str() );
      } else {
          /* Add the WHERE clause */
          m_poPrepStmt->Appendf( " WHERE (%s) = " CPL_FRMT_GIB, pszFIDColumn,
                                 poFeature->GetFID());
      }
      if (!m_poPrepStmt->DB2Prepare("OGR_DB2TableLayer::PrepareFeature"))
      {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "PREPARE command for feature failed. %s",
                    poDS->GetSession()->GetLastError() );
          return OGRERR_FAILURE;
      }
  
      return OGRERR_NONE;
  }
  
  /************************************************************************/
  /*                          ICreateFeature()                            */
  /************************************************************************/
  
  OGRErr OGRDB2TableLayer::ICreateFeature( OGRFeature *poFeature )
  {
      GetLayerDefn();
  
      if( NULL == poFeature )
      {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "NULL pointer to OGRFeature passed to CreateFeature()." );
          return OGRERR_FAILURE;
      }
  
      if (PrepareFeature(poFeature, 'I'))
          return OGRERR_FAILURE;
  
      char    *pszWKT = NULL;
      int nFieldCount = poFeatureDefn->GetFieldCount();
      int nBindNum = 0;
      void** papBindBuffer = (void**)CPLMalloc(sizeof(void*) 
                                                  * (nFieldCount + 1));
      OGRGeometry *poGeom = poFeature->GetGeometryRef();
  
      if (poGeom != NULL && pszGeomColumn != NULL)
      {
          if( poGeom->exportToWkt( &pszWKT ) == OGRERR_NONE)
          {
              int nLen = (int) strlen(pszWKT);
              if (m_poPrepStmt->DB2BindParameterIn(
                          "OGRDB2TableLayer::ICreateFeature",
                          (nBindNum + 1),
                          SQL_C_CHAR,
                          SQL_LONGVARCHAR,
                          nLen,
                          (void *)(pszWKT)))
              {
                  papBindBuffer[nBindNum] = pszWKT;
                  nBindNum++;
  
              } else {
                  CPLDebug("OGRDB2TableLayer::ICreateFeature",
                           "Bind parameter failed");
                  FreeBindBuffer(nBindNum, papBindBuffer);
                  return OGRERR_FAILURE;
              }
          }
      }
  
  // Explicitly add FID column and value if needed
      if( poFeature->GetFID() != OGRNullFID
              && pszFIDColumn != NULL && cGenerated != 'A' )
      {
          GIntBig nFID = poFeature->GetFID();
          if (m_poPrepStmt->DB2BindParameterIn(
                      "OGRDB2TableLayer::ICreateFeature",
                      (nBindNum + 1),
                      SQL_C_SBIGINT,
                      SQL_BIGINT,
                      sizeof(GIntBig),
                      (void *)(&nFID)))
          {
              papBindBuffer[nBindNum] = NULL;
              nBindNum++;
  
          } else {
              CPLDebug("OGRDB2TableLayer::ICreateFeature",
                       "Bind parameter failed");
              FreeBindBuffer(nBindNum, papBindBuffer);
              return OGRERR_FAILURE;
          }
      }
  
      for( int i = 0; i < nFieldCount; i++ )
      {
  
          if( !poFeature->IsFieldSet( i ) )
              continue;
  
  //        int nOGRFieldType = poFeatureDefn->GetFieldDefn(i)->GetType();
  //        CPLDebug("OGRDB2TableLayer::ICreateFeature",
  //               "i: %d; nOGRFieldType: %d",
  //                i, nOGRFieldType);
  
          if (BindFieldValue(m_poPrepStmt,
                             poFeature, i,
                             nBindNum, papBindBuffer) != OGRERR_NONE) {
              CPLDebug("OGRDB2TableLayer::ICreateFeature",
                       "Bind parameter failed");
              FreeBindBuffer(nBindNum, papBindBuffer);
              return OGRERR_FAILURE;
          }
          nBindNum++;
      }
  
      poDS->getDTime();
      /* -------------------------------------------------------------------- */
      /*      Execute the insert.                                             */
      /* -------------------------------------------------------------------- */
  
      if (!m_poPrepStmt->DB2Execute("OGR_DB2TableLayer::ICreateFeature"))
      {
          CPLError( CE_Failure, CPLE_AppDefined,
                    "INSERT command for new feature failed. %s",
                    poDS->GetSession()->GetLastError() );
          FreeBindBuffer(nBindNum, papBindBuffer);
          return OGRERR_FAILURE;
      }
      poDS->getDTime();
  
      if( bIsIdentityFid) {
          GIntBig oldFID = poFeature->GetFID();
          OGRDB2Statement oStatement2( poDS->GetSession() );
          oStatement2.Append( "select IDENTITY_VAL_LOCAL() AS IDENTITY "
                              "FROM SYSIBM.SYSDUMMY1");
          if( oStatement2.DB2Execute("OGR_DB2TableLayer::ICreateFeature")
                  && oStatement2.Fetch() )
          {
              poFeature->SetFID( atoi(oStatement2.GetColData( 0 ) ));
  
              if ( oStatement2.GetColData( 0 ) )
              {
                  poFeature->SetFID( atoi(oStatement2.GetColData( 0 ) ));
  
              }
          }
          CPLDebug("OGR_DB2TableLayer::ICreateFeature","Old FID: " CPL_FRMT_GIB
                   "; New FID: " CPL_FRMT_GIB, oldFID, poFeature->GetFID());
      }
  
      FreeBindBuffer(nBindNum, papBindBuffer);
  
      return OGRERR_NONE;
  }
  
  /************************************************************************/
  /*                          FreeBindBuffer()                            */
  /************************************************************************/
  
  void OGRDB2TableLayer::FreeBindBuffer(int nBindNum, void **papBindBuffer)
  {
      for( int i = 0; i < nBindNum; i++ ) {
          if (papBindBuffer[i] ) CPLFree(papBindBuffer[i]); // only free if set
      };
      CPLFree(papBindBuffer);
  }
  
  /************************************************************************/
  /*                          BindFieldValue()                            */
  /*                                                                      */
  /* Used by CreateFeature() and SetFeature() to bind a                   */
  /* non-empty field value                                                */
  /************************************************************************/
  
  OGRErr OGRDB2TableLayer::BindFieldValue(OGRDB2Statement *poStatement,
                                          OGRFeature* poFeature, int i,
                                          int nBindNum, void **papBindBuffer)
  {
      int nOGRFieldType = poFeatureDefn->GetFieldDefn(i)->GetType();
  
      int nLen = 0;
      void * pValuePointer = NULL;
      int nValueType = 0;
      int nParameterType = 0;
  
      if( nOGRFieldType == OFTString ) {
          const char *stringValue = NULL;
          stringValue = poFeature->GetFieldAsString(i);
          papBindBuffer[nBindNum] = NULL; // Don't free
          nLen = (int) strlen(stringValue);
          pValuePointer = (void *) stringValue;
          nValueType = SQL_C_CHAR;
          nParameterType = SQL_VARCHAR;
      }
  
      if ( nOGRFieldType == OFTReal ) {
          double *pnRealValue = (double *)CPLMalloc(sizeof(double));
          papBindBuffer[nBindNum] = pnRealValue;
          *pnRealValue = poFeature->GetFieldAsInteger(i);
          nLen = sizeof(double);
          pValuePointer = (void *) pnRealValue;
          nValueType = SQL_C_DOUBLE;
          nParameterType = SQL_DOUBLE;
      }
  
      if ( nOGRFieldType == OFTInteger ) {
          int *pnIntValue = (int *)CPLMalloc(sizeof(int));
          papBindBuffer[nBindNum] = pnIntValue;
          *pnIntValue = poFeature->GetFieldAsInteger(i);
          nLen = sizeof(int);
          pValuePointer = (void *) pnIntValue;
          nValueType = SQL_C_SLONG;
          nParameterType = SQL_INTEGER;
      }
  
      if ( nOGRFieldType == OFTInteger64 ) {
          GIntBig *pnLongValue = (GIntBig *)CPLMalloc(sizeof(GIntBig));
          papBindBuffer[nBindNum] = pnLongValue;
          *pnLongValue = poFeature->GetFieldAsInteger64(i);
          nLen = sizeof(GIntBig);
          pValuePointer = (void *) pnLongValue;
          nValueType = SQL_C_SBIGINT;
          nParameterType = SQL_BIGINT;
      }
  
  
      if (pValuePointer) {
          if (!m_poPrepStmt->DB2BindParameterIn(
                      "OGRDB2TableLayer::BindFieldValue",
                      (nBindNum + 1),
                      nValueType,
                      nParameterType,
                      nLen,
                      pValuePointer))
          {
              CPLDebug("OGRDB2TableLayer::BindFieldValue",
                       "Bind parameter failed");
              return OGRERR_FAILURE;
          }
      }
      return OGRERR_NONE;
  }
  
  
  /************************************************************************/
  /*                     CreateSpatialIndexIfNecessary()                  */
  /************************************************************************/
  
  void OGRDB2TableLayer::CreateSpatialIndexIfNecessary()
  {
      if( bDeferedSpatialIndexCreation )
      {
          CreateSpatialIndex();
      }
  }
  
  
  
  /************************************************************************/
  /*                      RunDeferredCreationIfNecessary()                */
  /************************************************************************/
  
  OGRErr OGRDB2TableLayer::RunDeferredCreationIfNecessary()
  {
      CPLDebug("OGRDB2TableLayer::RunDeferredCreationIfNecessary","NO-OP");
  #ifdef LATER
      if( !m_bDeferredCreation )
          return OGRERR_NONE;
      m_bDeferredCreation = FALSE;
  
      const char* pszLayerName = m_poFeatureDefn->GetName();
      OGRwkbGeometryType eGType = GetGeomType();
  
      int bIsSpatial = (eGType != wkbNone);
  
   /* Requirement 25: The geometry_type_name value in a gpkg_geometry_columns */
   /* row SHALL be one of the uppercase geometry type names specified in */
   /* Geometry Types (Normative). */
      const char *pszGeometryType = m_poDS->GetGeometryTypeString(eGType);
  
      /* Create the table! */
      char *pszSQL = NULL;
      CPLString osCommand;
  
      pszSQL = sqlite3_mprintf(
                   "CREATE TABLE \"%s\" ( "
                   "\"%s\" INTEGER PRIMARY KEY AUTOINCREMENT",
                   pszLayerName, m_pszFidColumn);
      osCommand += pszSQL;
      sqlite3_free(pszSQL);
  
      if( GetGeomType() != wkbNone )
      {
          pszSQL = sqlite3_mprintf(", '%q' %s",
                                   GetGeometryColumn(), pszGeometryType);
          osCommand += pszSQL;
          sqlite3_free(pszSQL);
          if( !m_poFeatureDefn->GetGeomFieldDefn(0)->IsNullable() )
          {
              osCommand += " NOT NULL";
          }
      }
  
      for(int i = 0; i < m_poFeatureDefn->GetFieldCount(); i++ )
      {
          if( i == m_iFIDAsRegularColumnIndex )
              continue;
          OGRFieldDefn* poFieldDefn = m_poFeatureDefn->GetFieldDefn(i);
          pszSQL = sqlite3_mprintf(", '%q' %s",
                                   poFieldDefn->GetNameRef(),
                                   GPkgFieldFromOGR(poFieldDefn->GetType(),
                                           poFieldDefn->GetSubType(),
                                           poFieldDefn->GetWidth()));
          osCommand += pszSQL;
          sqlite3_free(pszSQL);
          if( !poFieldDefn->IsNullable() )
          {
              osCommand += " NOT NULL";
          }
          const char* pszDefault = poFieldDefn->GetDefault();
          if( pszDefault != NULL &&
                  (!poFieldDefn->IsDefaultDriverSpecific() ||
                   (pszDefault[0] == '(' 
                   && pszDefault[strlen(pszDefault)-1] == ')' 
                   && (STARTS_WITH_CI(pszDefault+1, "strftime")
                   || STARTS_WITH_CI(pszDefault+1, " strftime")))) )
          {
              osCommand += " DEFAULT ";
              OGRField sField;
              if( poFieldDefn->GetType() == OFTDateTime &&
                      OGRParseDate(pszDefault, &sField, 0) )
              {
                  char* pszXML = OGRGetXMLDateTime(&sField);
                  osCommand += pszXML;
                  CPLFree(pszXML);
              }
  /* Make sure CURRENT_TIMESTAMP is translated into appropriate format */
  /* for GeoPackage */
              else if( poFieldDefn->GetType() == OFTDateTime &&
                       EQUAL(pszDefault, "CURRENT_TIMESTAMP") )
              {
                  osCommand += "(strftime('%Y-%m-%dT%H:%M:%fZ','now'))";
              }
              else
              {
                  osCommand += poFieldDefn->GetDefault();
              }
          }
      }
  
      osCommand += ")";
  
  
      OGRErr err = SQLCommand(m_poDS->GetDB(), osCommand.c_str());
      if ( OGRERR_NONE != err )
          return OGRERR_FAILURE;
  
      /* Update gpkg_contents with the table info */
      if ( bIsSpatial )
          err = RegisterGeometryColumn();
      else
          err = m_poDS->CreateGDALAspatialExtension();
  
      if ( err != OGRERR_NONE )
          return OGRERR_FAILURE;
  
      const char* pszIdentifier = GetMetadataItem("IDENTIFIER");
      if( pszIdentifier == NULL )
          pszIdentifier = pszLayerName;
      const char* pszDescription = GetMetadataItem("DESCRIPTION");
      if( pszDescription == NULL )
          pszDescription = "";
      pszSQL = sqlite3_mprintf(
                   "INSERT INTO gpkg_contents "
                   "(table_name,data_type,identifier,description,"
                   "last_change,srs_id)"
                   " VALUES "
                   "('%q','%q','%q','%q',strftime('%%Y-%%m-%%dT%%H:%%M:%%fZ',"
                   "CURRENT_TIMESTAMP),%d)",
                   pszLayerName, (bIsSpatial ? "features": "aspatial"), 
                   pszIdentifier, pszDescription, m_iSrs);
  
      err = SQLCommand(m_poDS->GetDB(), pszSQL);
      sqlite3_free(pszSQL);
      if ( err != OGRERR_NONE )
          return OGRERR_FAILURE;
  
      ResetReading();
  #endif
      return OGRERR_NONE;
  }