/******************************************************************************
 * $Id$
 *
 * Project:  GeoPackage Translator
 * Purpose:  Implements OGRGeoPackageTableLayer class
 * Author:   Paul Ramsey <pramsey@boundlessgeo.com>
 *
 ******************************************************************************
 * Copyright (c) 2013, Paul Ramsey <pramsey@boundlessgeo.com>
 * Copyright (c) 2014, Even Rouault <even dot rouault at mines-paris dot org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "ogr_geopackage.h"
#include "ogrgeopackageutility.h"
#include "cpl_time.h"
#include "ogr_p.h"

//----------------------------------------------------------------------
// SaveExtent()
//
// Write the current contents of the layer envelope down to the
// gpkg_contents metadata table.
//
OGRErr OGRGeoPackageTableLayer::SaveExtent()
{
    if ( !m_poDS->GetUpdate() || ! m_bExtentChanged || ! m_poExtent )
        return OGRERR_NONE;

    sqlite3* poDb = m_poDS->GetDB();

    if ( ! poDb ) return OGRERR_FAILURE;

    char *pszSQL = sqlite3_mprintf(
                "UPDATE gpkg_contents SET "
                "min_x = %g, min_y = %g, "
                "max_x = %g, max_y = %g "
                "WHERE table_name = '%q' AND "
                "Lower(data_type) = 'features'",
                m_poExtent->MinX, m_poExtent->MinY,
                m_poExtent->MaxX, m_poExtent->MaxY,
                m_pszTableName);

    OGRErr err = SQLCommand(poDb, pszSQL);
    sqlite3_free(pszSQL);
    m_bExtentChanged = false;

    return err;
}

//----------------------------------------------------------------------
// UpdateExtent()
//
// Expand the layer envelope if necessary to reflect the bounds
// of new features being added to the layer.
//
OGRErr OGRGeoPackageTableLayer::UpdateExtent( const OGREnvelope *poExtent )
{
    if ( ! m_poExtent )
    {
        m_poExtent = new OGREnvelope( *poExtent );
    }
    m_poExtent->Merge( *poExtent );
    m_bExtentChanged = true;
    return OGRERR_NONE;
}

//----------------------------------------------------------------------
// BuildColumns()
//
// Save a list of columns (fid, geometry, attributes) suitable
// for use in a SELECT query that retrieves all fields.
//
OGRErr OGRGeoPackageTableLayer::BuildColumns()
{
    if ( ! m_poFeatureDefn )
    {
        return OGRERR_FAILURE;
    }

    CPLFree(panFieldOrdinals);
    panFieldOrdinals = (int *) CPLMalloc( sizeof(int) * m_poFeatureDefn->GetFieldCount() );

    /* Always start with a primary key */
    CPLString soColumns = m_pszFidColumn ? m_pszFidColumn : "_rowid_";
    CPLString soColumn;
    iFIDCol = 0;

    /* Add a geometry column if there is one (just one) */
    if ( m_poFeatureDefn->GetGeomFieldCount() )
    {
        soColumns += ", ";
        soColumn.Printf("\"%s\"", m_poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef());
        soColumns += soColumn;
        iGeomCol = 1;
    }

    /* Add all the attribute columns */
    for( int i = 0; i < m_poFeatureDefn->GetFieldCount(); i++ )
    {
        soColumns += ", ";
        soColumn.Printf("\"%s\"", m_poFeatureDefn->GetFieldDefn(i)->GetNameRef());
        soColumns += soColumn;
        panFieldOrdinals[i] = 1 + (iGeomCol >= 0) + i;
    }

    m_soColumns = soColumns;
    return OGRERR_NONE;
}

//----------------------------------------------------------------------
// IsGeomFieldSet()
//
// Utility method to determine if there is a non-Null geometry
// in an OGRGeometry.
//
OGRBoolean OGRGeoPackageTableLayer::IsGeomFieldSet( OGRFeature *poFeature )
{
    if ( poFeature->GetDefnRef()->GetGeomFieldCount() &&
         poFeature->GetGeomFieldRef(0) )
    {
        return TRUE;
    }

    return FALSE;
}

OGRErr OGRGeoPackageTableLayer::FeatureBindParameters( OGRFeature *poFeature,
                                                       sqlite3_stmt *poStmt,
                                                       int *pnColCount,
                                                       bool bAddFID,
                                                       bool bBindNullFields )
{
    int nColCount = 1;
    int err;

    if ( ! (poFeature && poStmt && pnColCount) )
        return OGRERR_FAILURE;

    OGRFeatureDefn *poFeatureDefn = poFeature->GetDefnRef();

    if( bAddFID )
    {
        err = sqlite3_bind_int64(poStmt, nColCount++, poFeature->GetFID());
        if ( err != SQLITE_OK )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "failed to bind FID to statement");
            return OGRERR_FAILURE;
        }
    }

    /* Bind data values to the statement, here bind the blob for geometry */
    if ( poFeatureDefn->GetGeomFieldCount() )
    {
        GByte *pabyWkb = NULL;

        /* Non-NULL geometry */
        OGRGeometry* poGeom = poFeature->GetGeomFieldRef(0);
        if ( poGeom )
        {
            size_t szWkb;
            pabyWkb = GPkgGeometryFromOGR(poGeom, m_iSrs, &szWkb);
            err = sqlite3_bind_blob(poStmt, nColCount++, pabyWkb,
                                    static_cast<int>(szWkb), CPLFree);

            // FIXME: in case the geometry is a GeometryCollection, we should
            // inspect its subgeometries to see if there's non-linear ones.
            if( OGR_GT_IsNonLinear(poGeom->getGeometryType()) )
                CreateGeometryExtensionIfNecessary(poGeom->getGeometryType());
        }
        /* NULL geometry */
        else
        {
            err = sqlite3_bind_null(poStmt, nColCount++);
        }
        if ( err != SQLITE_OK )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "failed to bind geometry to statement");
            return OGRERR_FAILURE;
        }
    }

    /* Bind the attributes using appropriate SQLite data types */
    err = SQLITE_OK;
    for( int i = 0; err == SQLITE_OK && i < poFeatureDefn->GetFieldCount(); i++ )
    {
        if( i == m_iFIDAsRegularColumnIndex )
            continue;
        OGRFieldDefn *poFieldDefn = poFeatureDefn->GetFieldDefn(i);

        if( poFeature->IsFieldSet(i) )
        {
            switch(SQLiteFieldFromOGR(poFieldDefn->GetType()))
            {
                case SQLITE_INTEGER:
                {
                    err = sqlite3_bind_int64(poStmt, nColCount++, poFeature->GetFieldAsInteger64(i));
                    break;
                }
                case SQLITE_FLOAT:
                {
                    err = sqlite3_bind_double(poStmt, nColCount++, poFeature->GetFieldAsDouble(i));
                    break;
                }
                case SQLITE_BLOB:
                {
                    int szBlob;
                    GByte *pabyBlob = poFeature->GetFieldAsBinary(i, &szBlob);
                    err = sqlite3_bind_blob(poStmt, nColCount++, pabyBlob, szBlob, NULL);
                    break;
                }
                default:
                {
                    const char *pszVal = poFeature->GetFieldAsString(i);
                    int nValLengthBytes = (int)strlen(pszVal);
                    char szVal[32];
                    int nYear, nMonth, nDay, nHour, nMinute, nSecond, nTZFlag;
                    CPLString osTemp;
                    if( poFieldDefn->GetType() == OFTDate )
                    {
                        poFeature->GetFieldAsDateTime(i, &nYear, &nMonth, &nDay, &nHour, &nMinute, &nSecond, &nTZFlag);
                        snprintf(szVal, sizeof(szVal), "%04d-%02d-%02d", nYear, nMonth, nDay);
                        pszVal = szVal;
                        nValLengthBytes = (int)strlen(pszVal);
                    }
                    else if( poFieldDefn->GetType() == OFTDateTime )
                    {
                        float fSecond;
                        poFeature->GetFieldAsDateTime(i, &nYear, &nMonth, &nDay, &nHour, &nMinute, &fSecond, &nTZFlag);
                        if( nTZFlag == 0 || nTZFlag == 100 )
                        {
                            if( OGR_GET_MS(fSecond) )
                                snprintf(szVal, sizeof(szVal), "%04d-%02d-%02dT%02d:%02d:%06.3fZ",
                                     nYear, nMonth, nDay, nHour, nMinute, fSecond);
                            else
                                snprintf(szVal, sizeof(szVal), "%04d-%02d-%02dT%02d:%02d:%02dZ",
                                     nYear, nMonth, nDay, nHour, nMinute, (int)fSecond);
                            pszVal = szVal;
                            nValLengthBytes = (int)strlen(pszVal);
                        }
                    }
                    else if( poFieldDefn->GetType() == OFTString &&
                             poFieldDefn->GetWidth() > 0 )
                    {
                        if( !CPLIsUTF8(pszVal, -1) )
                        {
                            CPLError(CE_Warning, CPLE_AppDefined,
                                     "Value of field '%s' is not a valid UTF-8 string.%s",
                                     poFeatureDefn->GetFieldDefn(i)->GetNameRef(),
                                     m_bTruncateFields ? " Value will be laundered." : "");
                            if( m_bTruncateFields )
                            {
                                char* pszTemp = CPLForceToASCII(pszVal, -1, '_');
                                osTemp = pszTemp;
                                pszVal = osTemp.c_str();
                                CPLFree(pszTemp);
                            }
                        }

                        if( CPLStrlenUTF8(pszVal) > poFieldDefn->GetWidth() )
                        {
                            CPLError(CE_Warning, CPLE_AppDefined,
                                     "Value of field '%s' has %d characters, whereas maximum allowed is %d.%s",
                                     poFeatureDefn->GetFieldDefn(i)->GetNameRef(),
                                     CPLStrlenUTF8(pszVal),
                                     poFieldDefn->GetWidth(),
                                     m_bTruncateFields ? " Value will be truncated." : "");
                            if( m_bTruncateFields )
                            {
                                int k = 0;
                                nValLengthBytes = 0;
                                while (pszVal[nValLengthBytes])
                                {
                                    if ((pszVal[nValLengthBytes] & 0xc0) != 0x80)
                                    {
                                        k++;
                                        // Stop at the start of the character just beyond the maximum accepted
                                        if( k > poFieldDefn->GetWidth() )
                                            break;
                                    }
                                    nValLengthBytes++;
                                }
                            }
                        }
                    }
                    err = sqlite3_bind_text(poStmt, nColCount++, pszVal, nValLengthBytes, SQLITE_TRANSIENT);
                    break;
                }
            }
        }
        else
        {
            if( bBindNullFields )
                err = sqlite3_bind_null(poStmt, nColCount++);
        }
    }

    *pnColCount = nColCount;
    return (err == SQLITE_OK) ? OGRERR_NONE : OGRERR_FAILURE;
}

//----------------------------------------------------------------------
// FeatureBindUpdateParameters()
//
// Selectively bind the values of an OGRFeature to a prepared
// statement, prior to execution. Carefully binds exactly the
// same parameters that have been set up by FeatureGenerateUpdateSQL()
// as bindable.
//
OGRErr OGRGeoPackageTableLayer::FeatureBindUpdateParameters( OGRFeature *poFeature, sqlite3_stmt *poStmt )
{

    int nColCount;
    OGRErr err = FeatureBindParameters( poFeature, poStmt, &nColCount, false, true );
    if ( err != OGRERR_NONE )
        return err;

    /* Bind the FID to the "WHERE" clause */
    int sqlite_err = sqlite3_bind_int64(poStmt, nColCount, poFeature->GetFID());
    if ( sqlite_err != SQLITE_OK )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "failed to bind FID '" CPL_FRMT_GIB "' to statement", poFeature->GetFID());
        return OGRERR_FAILURE;
    }

    return OGRERR_NONE;
}


//----------------------------------------------------------------------
// FeatureBindInsertParameters()
//
// Selectively bind the values of an OGRFeature to a prepared
// statement, prior to execution. Carefully binds exactly the
// same parameters that have been set up by FeatureGenerateInsertSQL()
// as bindable.
//
OGRErr OGRGeoPackageTableLayer::FeatureBindInsertParameters( OGRFeature *poFeature,
                                                             sqlite3_stmt *poStmt,
                                                             bool bAddFID,
                                                             bool bBindNullFields )
{
    int nColCount;
    return FeatureBindParameters( poFeature, poStmt, &nColCount, bAddFID, bBindNullFields );
}


//----------------------------------------------------------------------
// FeatureGenerateInsertSQL()
//
// Build a SQL INSERT statement that references all the columns in
// the OGRFeatureDefn, then prepare it for repeated use in a prepared
// statement. All statements start off with geometry (if it exists)
// then reference each column in the order it appears in the OGRFeatureDefn.
// FeatureBindParameters operates on the expectation of this
// column ordering.
//
CPLString OGRGeoPackageTableLayer::FeatureGenerateInsertSQL( OGRFeature *poFeature,
                                                             bool bAddFID,
                                                             bool bBindNullFields )
{
    bool bNeedComma = false;
    OGRFeatureDefn *poFeatureDefn = poFeature->GetDefnRef();

    if( poFeatureDefn->GetFieldCount() == ((m_iFIDAsRegularColumnIndex >= 0) ? 1 : 0) &&
        poFeatureDefn->GetGeomFieldCount() == 0 &&
        !bAddFID )
        return CPLSPrintf("INSERT INTO \"%s\" DEFAULT VALUES", m_pszTableName);

    /* Set up our SQL string basics */
    CPLString osSQLFront;
    osSQLFront.Printf("INSERT INTO \"%s\" ( ", m_pszTableName);

    CPLString osSQLBack;
    osSQLBack = ") VALUES (";

    CPLString osSQLColumn;

    if( bAddFID )
    {
        osSQLColumn.Printf("\"%s\"", GetFIDColumn());
        osSQLFront += osSQLColumn;
        osSQLBack += "?";
        bNeedComma = true;
    }

    if ( poFeatureDefn->GetGeomFieldCount() )
    {
        if( bNeedComma )
        {
            osSQLFront += ", ";
            osSQLBack += ", ";
        }

        osSQLColumn.Printf("\"%s\"", poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef());
        osSQLFront += osSQLColumn;
        osSQLBack += "?";
        bNeedComma = true;
    }

    /* Add attribute column names (except FID) to the SQL */
    for( int i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
    {
        if( i == m_iFIDAsRegularColumnIndex )
            continue;
        if( !bBindNullFields && !poFeature->IsFieldSet(i) )
            continue;

        if( !bNeedComma )
        {
            bNeedComma = true;
        }
        else
        {
            osSQLFront += ", ";
            osSQLBack += ", ";
        }

        osSQLColumn.Printf("\"%s\"", poFeatureDefn->GetFieldDefn(i)->GetNameRef());
        osSQLFront += osSQLColumn;
        osSQLBack += "?";
    }

    osSQLBack += ")";

    if( !bNeedComma )
        return CPLSPrintf("INSERT INTO \"%s\" DEFAULT VALUES", m_pszTableName);

    return osSQLFront + osSQLBack;
}


//----------------------------------------------------------------------
// FeatureGenerateUpdateSQL()
//
// Build a SQL UPDATE statement that references all the columns in
// the OGRFeatureDefn, then prepare it for repeated use in a prepared
// statement. All statements start off with geometry (if it exists)
// then reference each column in the order it appears in the OGRFeatureDefn.
// FeatureBindParameters operates on the expectation of this
// column ordering.

//
CPLString OGRGeoPackageTableLayer::FeatureGenerateUpdateSQL( OGRFeature *poFeature )
{
    bool bNeedComma = false;
    OGRFeatureDefn *poFeatureDefn = poFeature->GetDefnRef();

    /* Set up our SQL string basics */
    CPLString osUpdate;
    osUpdate.Printf("UPDATE \"%s\" SET ", m_pszTableName);

    CPLString osSQLColumn;

    if ( poFeatureDefn->GetGeomFieldCount() > 0 )
    {
        osSQLColumn.Printf("\"%s\"", poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef());
        osUpdate += osSQLColumn;
        osUpdate += "=?";
        bNeedComma = true;
    }

    /* Add attribute column names (except FID) to the SQL */
    for( int i = 0; i < poFeatureDefn->GetFieldCount(); i++ )
    {
        if( i == m_iFIDAsRegularColumnIndex )
            continue;
        if( !bNeedComma )
            bNeedComma = true;
        else
            osUpdate += ", ";

        osSQLColumn.Printf("\"%s\"", poFeatureDefn->GetFieldDefn(i)->GetNameRef());
        osUpdate += osSQLColumn;
        osUpdate += "=?";
    }

    CPLString osWhere;
    osWhere.Printf(" WHERE \"%s\" = ?", m_pszFidColumn);

    return osUpdate + osWhere;
}


//----------------------------------------------------------------------
// ReadTableDefinition()
//
// Initialization routine. Read all the metadata about a table,
// starting from just the table name. Reads information from GPKG
// metadata tables and from SQLite table metadata. Uses it to
// populate OGRSpatialReference information and OGRFeatureDefn objects,
// among others.
//
OGRErr OGRGeoPackageTableLayer::ReadTableDefinition(int bIsSpatial)
{
    OGRErr err;
    SQLResult oResultTable;
    char* pszSQL;
    bool bReadExtent = false;
    sqlite3* poDb = m_poDS->GetDB();
    OGREnvelope oExtent;
    CPLString osGeomColumnName;
    CPLString osGeomColsType;
    bool bHasZ = false;
    bool bHasM = false;

    /* Check that the table name is registered in gpkg_contents */
    pszSQL = sqlite3_mprintf(
                "SELECT table_name, data_type, identifier, "
                "description, min_x, min_y, max_x, max_y, srs_id "
                "FROM gpkg_contents "
                "WHERE table_name = '%q'"
#ifdef WORKAROUND_SQLITE3_BUGS
                " OR 0"
#endif
                ,m_pszTableName);

    SQLResult oResultContents;
    err = SQLQuery(poDb, pszSQL, &oResultContents);
    sqlite3_free(pszSQL);

    /* gpkg_contents query has to work */
    /* gpkg_contents.table_name is supposed to be unique */
    if ( err != OGRERR_NONE || oResultContents.nRowCount != 1 )
    {
        if ( err != OGRERR_NONE )
            CPLError( CE_Failure, CPLE_AppDefined, "%s", oResultContents.pszErrMsg ? oResultContents.pszErrMsg : "" );
        else /* if ( oResultContents.nRowCount != 1 ) */
            CPLError( CE_Failure, CPLE_AppDefined, "layer '%s' is not registered in gpkg_contents", m_pszTableName );

        SQLResultFree(&oResultContents);
        return OGRERR_FAILURE;
    }

    const char* pszIdentifier = SQLResultGetValue(&oResultContents, 2, 0);
    if( pszIdentifier && strcmp(pszIdentifier, m_pszTableName) != 0 )
        OGRLayer::SetMetadataItem("IDENTIFIER", pszIdentifier);
    const char* pszDescription = SQLResultGetValue(&oResultContents, 3, 0);
    if( pszDescription && pszDescription[0] )
        OGRLayer::SetMetadataItem("DESCRIPTION", pszDescription);

    if( bIsSpatial )
    {
        const char *pszMinX = SQLResultGetValue(&oResultContents, 4, 0);
        const char *pszMinY = SQLResultGetValue(&oResultContents, 5, 0);
        const char *pszMaxX = SQLResultGetValue(&oResultContents, 6, 0);
        const char *pszMaxY = SQLResultGetValue(&oResultContents, 7, 0);

        /* All the extrema have to be non-NULL for this to make sense */
        if ( pszMinX && pszMinY && pszMaxX && pszMaxY )
        {
            oExtent.MinX = CPLAtof(pszMinX);
            oExtent.MinY = CPLAtof(pszMinY);
            oExtent.MaxX = CPLAtof(pszMaxX);
            oExtent.MaxY = CPLAtof(pszMaxY);
            bReadExtent = true;
        }

        /* Done with info from gpkg_contents now */
        SQLResultFree(&oResultContents);

        /* Check that the table name is registered in gpkg_geometry_columns */
        pszSQL = sqlite3_mprintf(
                    "SELECT table_name, column_name, "
                    "geometry_type_name, srs_id, z, m "
                    "FROM gpkg_geometry_columns "
                    "WHERE table_name = '%q'"
#ifdef WORKAROUND_SQLITE3_BUGS
                    " OR 0"
#endif
                    ,m_pszTableName);

        SQLResult oResultGeomCols;
        err = SQLQuery(poDb, pszSQL, &oResultGeomCols);
        sqlite3_free(pszSQL);

        /* gpkg_geometry_columns query has to work */
        /* gpkg_geometry_columns.table_name is supposed to be unique */
        if ( err != OGRERR_NONE || oResultGeomCols.nRowCount != 1 )
        {
            if ( err != OGRERR_NONE )
                CPLError( CE_Failure, CPLE_AppDefined, "%s", oResultGeomCols.pszErrMsg ? oResultGeomCols.pszErrMsg : "" );
            else /* if ( oResultContents.nRowCount != 1 ) */
                CPLError( CE_Failure, CPLE_AppDefined, "layer '%s' is not registered in gpkg_geometry_columns", m_pszTableName );

            SQLResultFree(&oResultGeomCols);
            return OGRERR_FAILURE;
        }

        const char* pszGeomColName = SQLResultGetValue(&oResultGeomCols, 1, 0);
        if( pszGeomColName != NULL )
            osGeomColumnName = pszGeomColName;
        const char* pszGeomColsType = SQLResultGetValue(&oResultGeomCols, 2, 0);
        if( pszGeomColsType != NULL )
            osGeomColsType = pszGeomColsType;
        m_iSrs = SQLResultGetValueAsInteger(&oResultGeomCols, 3, 0);
        bHasZ = CPL_TO_BOOL(SQLResultGetValueAsInteger(&oResultGeomCols, 4, 0));
        bHasM = CPL_TO_BOOL(SQLResultGetValueAsInteger(&oResultGeomCols, 5, 0));

        SQLResultFree(&oResultGeomCols);
    }
    else
        SQLResultFree(&oResultContents);

    /* Use the "PRAGMA TABLE_INFO()" call to get table definition */
    /*  #|name|type|notnull|default|pk */
    /*  0|id|integer|0||1 */
    /*  1|name|varchar|0||0 */
    pszSQL = sqlite3_mprintf("pragma table_info('%q')", m_pszTableName);
    err = SQLQuery(poDb, pszSQL, &oResultTable);
    sqlite3_free(pszSQL);

    if ( err != OGRERR_NONE || oResultTable.nRowCount == 0 )
    {
        if( oResultTable.pszErrMsg != NULL )
            CPLError( CE_Failure, CPLE_AppDefined, "%s", oResultTable.pszErrMsg );
        else
            CPLError( CE_Failure, CPLE_AppDefined, "Cannot find table %s", m_pszTableName );

        SQLResultFree(&oResultTable);
        return OGRERR_FAILURE;
    }

    /* Populate feature definition from table description */
    m_poFeatureDefn = new OGRFeatureDefn( m_pszTableName );
    SetDescription( m_poFeatureDefn->GetName() );
    m_poFeatureDefn->SetGeomType(wkbNone);
    m_poFeatureDefn->Reference();

    int iRecord;
    bool bFidFound = false;

    for ( iRecord = 0; iRecord < oResultTable.nRowCount; iRecord++ )
    {
        const char *pszName = SQLResultGetValue(&oResultTable, 1, iRecord);
        const char *pszType = SQLResultGetValue(&oResultTable, 2, iRecord);
        int bNotNull = SQLResultGetValueAsInteger(&oResultTable, 3, iRecord);
        const char* pszDefault = SQLResultGetValue(&oResultTable, 4, iRecord);
        OGRBoolean bFid = SQLResultGetValueAsInteger(&oResultTable, 5, iRecord);
        OGRFieldSubType eSubType;
        int nMaxWidth;
        OGRFieldType oType = GPkgFieldToOGR(pszType, eSubType, nMaxWidth);

        /* Not a standard field type... */
        if ( (oType > OFTMaxType && osGeomColsType.size()) || EQUAL(osGeomColumnName, pszName) )
        {
            /* Maybe it's a geometry type? */
            OGRwkbGeometryType oGeomType;
            if( oType > OFTMaxType )
                oGeomType = GPkgGeometryTypeToWKB(pszType, bHasZ, bHasM);
            else
                oGeomType = wkbUnknown;
            if ( oGeomType != wkbNone )
            {
                OGRwkbGeometryType oGeomTypeGeomCols = GPkgGeometryTypeToWKB(osGeomColsType.c_str(), bHasZ, bHasM);
                /* Enforce consistency between table and metadata */
                if( wkbFlatten(oGeomType) == wkbUnknown )
                    oGeomType = oGeomTypeGeomCols;
                if ( oGeomType != oGeomTypeGeomCols )
                {
                    CPLError(CE_Warning, CPLE_AppDefined,
                             "geometry column type in '%s.%s' is not consistent with type in gpkg_geometry_columns",
                             m_pszTableName, pszName);
                }

                if ( m_poFeatureDefn->GetGeomFieldCount() == 0 )
                {
                    OGRGeomFieldDefn oGeomField(pszName, oGeomType);
                    if( bNotNull )
                        oGeomField.SetNullable(FALSE);
                    m_poFeatureDefn->AddGeomFieldDefn(&oGeomField);

                    /* Read the SRS */
                    OGRSpatialReference *poSRS = m_poDS->GetSpatialRef(m_iSrs);
                    if ( poSRS )
                    {
                        m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
                        poSRS->Dereference();
                    }
                }
                else
                {
                    CPLError(CE_Failure, CPLE_AppDefined,
                             "table '%s' has multiple geometry fields? not legal in gpkg",
                             m_pszTableName);
                    SQLResultFree(&oResultTable);
                    return OGRERR_FAILURE;
                }

            }
            else
            {
                // CPLError( CE_Failure, CPLE_AppDefined, "invalid field type '%s'", pszType );
                // SQLResultFree(&oResultTable);
                CPLError(CE_Warning, CPLE_AppDefined,
                         "geometry column '%s' of type '%s' ignored", pszName, pszType);
            }

        }
        else
        {
            /* Is this the FID column? */
            if ( bFid && (oType == OFTInteger || oType == OFTInteger64) )
            {
                if( bFidFound )
                {
                    CPLDebug("GPKG", "For table %s, a new FID column has been found (%s). Keeping previous one (%s)",
                             m_pszTableName, pszName, m_pszFidColumn);
                }
                else
                {
                    bFidFound = true;
                    m_pszFidColumn = CPLStrdup(pszName);
                }
            }
            else
            {
                OGRFieldDefn oField(pszName, oType);
                oField.SetSubType(eSubType);
                oField.SetWidth(nMaxWidth);
                if( bNotNull )
                    oField.SetNullable(FALSE);
                if( pszDefault != NULL )
                {
                    int nYear, nMonth, nDay, nHour, nMinute;
                    float fSecond;
                    if( oField.GetType() == OFTString &&
                        !EQUAL(pszDefault, "NULL") &&
                        !STARTS_WITH_CI(pszDefault, "CURRENT_") &&
                        pszDefault[0] != '(' &&
                        pszDefault[0] != '\'' &&
                        CPLGetValueType(pszDefault) == CPL_VALUE_STRING )
                    {
                        CPLString osDefault("'");
                        char* pszTmp = CPLEscapeString(pszDefault, -1, CPLES_SQL);
                        osDefault += pszTmp;
                        CPLFree(pszTmp);
                        osDefault += "'";
                        oField.SetDefault(osDefault);
                    }
                    else if( oType == OFTDateTime &&
                             sscanf(pszDefault, "'%d-%d-%dT%d:%d:%fZ'", &nYear, &nMonth, &nDay,
                                        &nHour, &nMinute, &fSecond) == 6 )
                    {
                        if( strchr(pszDefault, '.') == NULL )
                            oField.SetDefault(CPLSPrintf("'%04d/%02d/%02d %02d:%02d:%02d'",
                                                      nYear, nMonth, nDay, nHour, nMinute, (int)(fSecond+0.5)));
                        else
                            oField.SetDefault(CPLSPrintf("'%04d/%02d/%02d %02d:%02d:%06.3f'",
                                                            nYear, nMonth, nDay, nHour, nMinute, fSecond));
                    }
                    else if( (oField.GetType() == OFTDate || oField.GetType() == OFTDateTime) &&
                             !EQUAL(pszDefault, "NULL") &&
                             !STARTS_WITH_CI(pszDefault, "CURRENT_") &&
                             pszDefault[0] != '(' &&
                             pszDefault[0] != '\'' &&
                             !(pszDefault[0] >= '0' && pszDefault[0] <= '9') &&
                            CPLGetValueType(pszDefault) == CPL_VALUE_STRING )
                    {
                        CPLString osDefault("(");
                        osDefault += pszDefault;
                        osDefault += ")";
                        if( EQUAL(osDefault, "(strftime('%Y-%m-%dT%H:%M:%fZ','now'))") )
                            oField.SetDefault("CURRENT_TIMESTAMP");
                        else
                            oField.SetDefault(osDefault);
                    }
                    else
                    {
                        oField.SetDefault(pszDefault);
                    }
                }
                m_poFeatureDefn->AddFieldDefn(&oField);
            }
        }
    }

    /* Wait, we didn't find a FID? Some operations will not be possible */
    if ( ! bFidFound )
    {
        CPLDebug("GPKG",
                 "no integer primary key defined for table '%s'", m_pszTableName);
    }
    else
    {
    /* -------------------------------------------------------------------- */
    /*      Find if the FID holds 64bit values                              */
    /* -------------------------------------------------------------------- */
        const char* pszSQLStatic = CPLSPrintf("SELECT MAX(%s) FROM '%s'",
                            OGRSQLiteEscape(m_pszFidColumn).c_str(),
                            m_pszTableName);
        sqlite3_stmt* hColStmt = NULL;
        int rc = sqlite3_prepare( poDb, pszSQLStatic, -1, &hColStmt, NULL );
        if( rc == SQLITE_OK )
        {
            rc = sqlite3_step( hColStmt );
            if( rc == SQLITE_ROW )
            {
                GIntBig nMaxId = sqlite3_column_int64( hColStmt, 0 );
                if( nMaxId > INT_MAX )
                    OGRLayer::SetMetadataItem(OLMD_FID64, "YES");
            }
        }
        sqlite3_finalize( hColStmt );
    }

    if ( bReadExtent )
    {
        m_poExtent = new OGREnvelope(oExtent);
    }

    SQLResultFree(&oResultTable);

    /* Update the columns string */
    BuildColumns();

    CheckUnknownExtensions();

    return OGRERR_NONE;
}


/************************************************************************/
/*                      OGRGeoPackageTableLayer()                       */
/************************************************************************/

OGRGeoPackageTableLayer::OGRGeoPackageTableLayer(
                    GDALGeoPackageDataset *poDS,
                    const char * pszTableName) : OGRGeoPackageLayer(poDS)
{
    m_pszTableName = CPLStrdup(pszTableName);
    m_iSrs = 0;
    m_poExtent = NULL;
    m_bExtentChanged = false;
    m_poQueryStatement = NULL;
    m_poUpdateStatement = NULL;
    m_bInsertStatementWithFID = false;
    m_poInsertStatement = NULL;
    m_soColumns = "";
    m_soFilter = "";
    m_bDeferredSpatialIndexCreation = false;
    m_bHasSpatialIndex = -1;
    m_bDropRTreeTable = false;
    memset(m_abHasGeometryExtension, 0, sizeof(m_abHasGeometryExtension)); /* false */
    m_bPreservePrecision = true;
    m_bTruncateFields = false;
    m_bDeferredCreation = false;
    m_iFIDAsRegularColumnIndex = -1;
    m_bHasReadMetadataFromStorage = false;
}


/************************************************************************/
/*                      ~OGRGeoPackageTableLayer()                      */
/************************************************************************/

OGRGeoPackageTableLayer::~OGRGeoPackageTableLayer()
{
    if( m_bDeferredCreation )
        RunDeferredCreationIfNecessary();

    if( m_bDropRTreeTable )
    {
        const char* pszT = m_pszTableName;
        const char* pszC =m_poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef();
        char* pszSQL;
        pszSQL = sqlite3_mprintf("DROP TABLE \"rtree_%s_%s\"", pszT, pszC);
        SQLCommand(m_poDS->GetDB(), pszSQL);
        sqlite3_free(pszSQL);
    }
    else
    {
        CreateSpatialIndexIfNecessary();
    }

    /* Save metadata back to the database */
    SaveExtent();

    /* Clean up resources in memory */
    if ( m_pszTableName )
        CPLFree( m_pszTableName );

    if ( m_poExtent )
        delete m_poExtent;

    if ( m_poUpdateStatement )
        sqlite3_finalize(m_poUpdateStatement);

    if ( m_poInsertStatement )
        sqlite3_finalize(m_poInsertStatement);
}


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

OGRErr OGRGeoPackageTableLayer::CreateField( OGRFieldDefn *poField,
                                             CPL_UNUSED int bApproxOK )
{
    OGRFieldDefn oFieldDefn(poField);
    if( !m_poDS->GetUpdate() )
    {
        return OGRERR_FAILURE;
    }

    int nMaxWidth = 0;
    if( m_bPreservePrecision && poField->GetType() == OFTString )
        nMaxWidth = poField->GetWidth();
    else
        oFieldDefn.SetWidth(0);
    oFieldDefn.SetPrecision(0);

    if( m_pszFidColumn != NULL &&
        EQUAL( oFieldDefn.GetNameRef(), m_pszFidColumn ) &&
        oFieldDefn.GetType() != OFTInteger &&
        oFieldDefn.GetType() != OFTInteger64 )
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Wrong field type for %s",
                 oFieldDefn.GetNameRef());
        return OGRERR_FAILURE;
    }

    if( !m_bDeferredCreation )
    {
        CPLString osCommand;

        osCommand.Printf("ALTER TABLE \"%s\" ADD COLUMN \"%s\" %s",
                                 m_pszTableName, poField->GetNameRef(),
                                 GPkgFieldFromOGR(poField->GetType(),
                                                  poField->GetSubType(),
                                                  nMaxWidth));
        if(  !poField->IsNullable() )
            osCommand += " NOT NULL";
        if( poField->GetDefault() != NULL && !poField->IsDefaultDriverSpecific() )
        {
            osCommand += " DEFAULT ";
            int nYear, nMonth, nDay, nHour, nMinute;
            float fSecond;
            if( poField->GetType() == OFTDateTime &&
                sscanf(poField->GetDefault(), "'%d/%d/%d %d:%d:%f'", &nYear, &nMonth, &nDay,
                                        &nHour, &nMinute, &fSecond) == 6 )
            {
                if( strchr(poField->GetDefault(), '.') == NULL )
                    osCommand += CPLSPrintf("'%04d-%02d-%02dT%02d:%02d:%02dZ'",
                                        nYear, nMonth, nDay, nHour, nMinute, (int)(fSecond+0.5));
                else
                    osCommand += CPLSPrintf("'%04d-%02d-%02dT%02d:%02d:%06.3fZ'",
                                            nYear, nMonth, nDay, nHour, nMinute, fSecond);
            }
            else
                osCommand += poField->GetDefault();
        }
        else if( !poField->IsNullable() )
        {
            // This is kind of dumb, but SQLite mandates a DEFAULT value
            // when adding a NOT NULL column in an ALTER TABLE ADD COLUMN
            // statement, which defeats the purpose of NOT NULL,
            // whereas it doesn't in CREATE TABLE
            osCommand += " DEFAULT ''";
        }

        OGRErr err = SQLCommand(m_poDS->GetDB(), osCommand.c_str());

        if ( err != OGRERR_NONE )
            return err;
    }

    m_poFeatureDefn->AddFieldDefn( &oFieldDefn );

    if( m_pszFidColumn != NULL &&
        EQUAL( oFieldDefn.GetNameRef(), m_pszFidColumn ) )
    {
        m_iFIDAsRegularColumnIndex = m_poFeatureDefn->GetFieldCount() - 1;
    }

    if( !m_bDeferredCreation )
    {
        ResetReading();
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                           CreateGeomField()                          */
/************************************************************************/

OGRErr OGRGeoPackageTableLayer::CreateGeomField( OGRGeomFieldDefn *poGeomFieldIn,
                                                 CPL_UNUSED int bApproxOK )
{
    if( m_poFeatureDefn->GetGeomFieldCount() == 1 )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Cannot create more than on geometry field in GeoPackage");
        return OGRERR_FAILURE;
    }

    OGRwkbGeometryType eType = poGeomFieldIn->GetType();
    if( eType == wkbNone )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Cannot create geometry field of type wkbNone");
        return OGRERR_FAILURE;
    }

    OGRGeomFieldDefn oGeomField(poGeomFieldIn);
    if( EQUAL(oGeomField.GetNameRef(), "") )
    {
        oGeomField.SetName( "geom" );
    }

    OGRSpatialReference* poSRS = oGeomField.GetSpatialRef();
    if( poSRS != NULL )
        m_iSrs = m_poDS->GetSrsId(poSRS);

/* -------------------------------------------------------------------- */
/*      Create the new field.                                           */
/* -------------------------------------------------------------------- */
    if( !m_bDeferredCreation )
    {
        char *pszSQL;

        pszSQL = sqlite3_mprintf("ALTER TABLE \"%s\" ADD COLUMN \"%s\" %s%s",
                                 m_pszTableName, oGeomField.GetNameRef(),
                                 m_poDS->GetGeometryTypeString(oGeomField.GetType()),
                                 !oGeomField.IsNullable() ? " NOT NULL DEFAULT ''" : "");

        OGRErr err = SQLCommand(m_poDS->GetDB(), pszSQL);
        sqlite3_free(pszSQL);

        if ( err != OGRERR_NONE )
            return err;

        pszSQL = sqlite3_mprintf(
            "UPDATE gpkg_contents SET data_type = 'features' WHERE table_name = '%q'",
            GetName());
        err = SQLCommand(m_poDS->GetDB(), pszSQL);
        sqlite3_free(pszSQL);
        if ( err != OGRERR_NONE )
            return OGRERR_FAILURE;

        bool bHasASpatialLayers = false;
        for(int i=0;i<m_poDS->GetLayerCount();i++)
        {
            if( m_poDS->GetLayer(i) != this &&
                m_poDS->GetLayer(i)->GetLayerDefn()->GetGeomFieldCount() == 0 )
                bHasASpatialLayers = true;
        }
        if( !bHasASpatialLayers )
        {
            err = SQLCommand(m_poDS->GetDB(),
                             "DELETE FROM gpkg_extensions WHERE "
                             "extension_name = 'gdal_aspatial' "
                             "AND table_name IS NULL "
                             "AND column_name IS NULL");
            if ( err != OGRERR_NONE )
                return OGRERR_FAILURE;
        }
    }

    m_poFeatureDefn->AddGeomFieldDefn( &oGeomField );

    if( !m_bDeferredCreation )
    {
        OGRErr err = RegisterGeometryColumn();
        if ( err != OGRERR_NONE )
            return err;

        ResetReading();
    }

    return OGRERR_NONE;
}

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

OGRErr OGRGeoPackageTableLayer::ICreateFeature( OGRFeature *poFeature )
{
    if( !m_poDS->GetUpdate() )
    {
        return OGRERR_FAILURE;
    }

    if( m_bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE )
        return OGRERR_FAILURE;

    /* Substitute default values for null Date/DateTime fields as the standard */
    /* format of SQLite is not the one mandated by GeoPackage */
    poFeature->FillUnsetWithDefault(FALSE, NULL);
    bool bHasDefaultValue = false;
    int iField;
    int nFieldCount = m_poFeatureDefn->GetFieldCount();
    for( iField = 0; iField < nFieldCount; iField++ )
    {
        if( poFeature->IsFieldSet( iField ) )
            continue;
        const char* pszDefault = poFeature->GetFieldDefnRef(iField)->GetDefault();
        if( pszDefault != NULL )
        {
            bHasDefaultValue = true;
            break;
        }
    }

    /* In case the FID column has also been created as a regular field */
    if( m_iFIDAsRegularColumnIndex >= 0 )
    {
        if( poFeature->GetFID() == OGRNullFID )
        {
            if( poFeature->IsFieldSet( m_iFIDAsRegularColumnIndex ) )
            {
                poFeature->SetFID(
                    poFeature->GetFieldAsInteger64(m_iFIDAsRegularColumnIndex));
            }
        }
        else
        {
            if( !poFeature->IsFieldSet( m_iFIDAsRegularColumnIndex ) ||
                poFeature->GetFieldAsInteger64(m_iFIDAsRegularColumnIndex) != poFeature->GetFID() )
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                            "Inconsistent values of FID and field of same name");
                return OGRERR_FAILURE;
            }
        }
    }

    /* If there's a unset field with a default value, then we must create */
    /* a specific INSERT statement to avoid unset fields to be bound to NULL */
    if( m_poInsertStatement && (bHasDefaultValue || m_bInsertStatementWithFID != (poFeature->GetFID() != OGRNullFID)) )
    {
        sqlite3_finalize(m_poInsertStatement);
        m_poInsertStatement = NULL;
    }

    if ( ! m_poInsertStatement )
    {
        /* Construct a SQL INSERT statement from the OGRFeature */
        /* Only work with fields that are set */
        /* Do not stick values into SQL, use placeholder and bind values later */
        m_bInsertStatementWithFID = poFeature->GetFID() != OGRNullFID;
        CPLString osCommand = FeatureGenerateInsertSQL(poFeature, m_bInsertStatementWithFID, !bHasDefaultValue);

        /* Prepare the SQL into a statement */
        sqlite3 *poDb = m_poDS->GetDB();
        int err = sqlite3_prepare_v2(poDb, osCommand, -1, &m_poInsertStatement, NULL);
        if ( err != SQLITE_OK )
        {
            m_poInsertStatement = NULL;
            CPLError( CE_Failure, CPLE_AppDefined,
                      "failed to prepare SQL: %s", osCommand.c_str());
            return OGRERR_FAILURE;
        }
    }

    /* Bind values onto the statement now */
    OGRErr errOgr = FeatureBindInsertParameters(poFeature, m_poInsertStatement,
                                                m_bInsertStatementWithFID, !bHasDefaultValue);
    if ( errOgr != OGRERR_NONE )
    {
        sqlite3_reset(m_poInsertStatement);
        sqlite3_clear_bindings(m_poInsertStatement);
        sqlite3_finalize(m_poInsertStatement);
        m_poInsertStatement = NULL;
        return errOgr;
    }

    /* From here execute the statement and check errors */
    int err = sqlite3_step(m_poInsertStatement);
    if ( ! (err == SQLITE_OK || err == SQLITE_DONE) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "failed to execute insert : %s",
                  sqlite3_errmsg(m_poDS->GetDB()) ? sqlite3_errmsg(m_poDS->GetDB()) : "");
        sqlite3_reset(m_poInsertStatement);
        sqlite3_clear_bindings(m_poInsertStatement);
        sqlite3_finalize(m_poInsertStatement);
        m_poInsertStatement = NULL;
        return OGRERR_FAILURE;
    }

    sqlite3_reset(m_poInsertStatement);
    sqlite3_clear_bindings(m_poInsertStatement);

    if( bHasDefaultValue )
    {
        sqlite3_finalize(m_poInsertStatement);
        m_poInsertStatement = NULL;
    }

    /* Update the layer extents with this new object */
    if ( IsGeomFieldSet(poFeature) )
    {
        OGREnvelope oEnv;
        poFeature->GetGeomFieldRef(0)->getEnvelope(&oEnv);
        UpdateExtent(&oEnv);
    }

    /* Read the latest FID value */
    GIntBig nFID = sqlite3_last_insert_rowid(m_poDS->GetDB());
    if( nFID )
    {
        poFeature->SetFID(nFID);
        if( m_iFIDAsRegularColumnIndex >= 0 )
            poFeature->SetField( m_iFIDAsRegularColumnIndex, nFID );
    }
    else
    {
        poFeature->SetFID(OGRNullFID);
    }

    /* All done! */
    return OGRERR_NONE;
}


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

OGRErr OGRGeoPackageTableLayer::ISetFeature( OGRFeature *poFeature )
{
    if( !m_poDS->GetUpdate() || m_pszFidColumn == NULL )
    {
        return OGRERR_FAILURE;
    }

    /* No FID? We can't set, we have to create */
    if ( poFeature->GetFID() == OGRNullFID )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "FID required on features given to SetFeature()." );
        return OGRERR_FAILURE;
    }

    /* In case the FID column has also been created as a regular field */
    if( m_iFIDAsRegularColumnIndex >= 0 )
    {
        if( !poFeature->IsFieldSet( m_iFIDAsRegularColumnIndex ) ||
            poFeature->GetFieldAsInteger64(m_iFIDAsRegularColumnIndex) != poFeature->GetFID() )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                        "Inconsistent values of FID and field of same name");
            return OGRERR_FAILURE;
        }
    }

    if( m_bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE )
        return OGRERR_FAILURE;

    /* Old version of SQLite have issues with some of the spatial index triggers */
#if SQLITE_VERSION_NUMBER < 3007008
    if( HasSpatialIndex() )
    {
        if ( ! m_poUpdateStatement )
        {
            /* Construct a SQL INSERT statement from the OGRFeature */
            /* Only work with fields that are set */
            /* Do not stick values into SQL, use placeholder and bind values later */
            CPLString osCommand = FeatureGenerateInsertSQL(poFeature, true, true);

            /* Prepare the SQL into a statement */
            int err = sqlite3_prepare_v2(m_poDS->GetDB(), osCommand, -1, &m_poUpdateStatement, NULL);
            if ( err != SQLITE_OK )
            {
                CPLError( CE_Failure, CPLE_AppDefined,
                        "failed to prepare SQL: %s", osCommand.c_str());
                return OGRERR_FAILURE;
            }
        }

        sqlite3_stmt* hBackupStmt = m_poUpdateStatement;
        m_poUpdateStatement = NULL;

        OGRErr errOgr = DeleteFeature( poFeature->GetFID() );

        m_poUpdateStatement = hBackupStmt;

        if ( errOgr != OGRERR_NONE )
            return errOgr;

        /* Bind values onto the statement now */
        errOgr = FeatureBindInsertParameters(poFeature, m_poUpdateStatement, true, true);
        if ( errOgr != OGRERR_NONE )
            return errOgr;
    }
    else
#endif
    {
        if ( ! m_poUpdateStatement )
        {
            /* Construct a SQL UPDATE statement from the OGRFeature */
            /* Only work with fields that are set */
            /* Do not stick values into SQL, use placeholder and bind values later */
            CPLString osCommand = FeatureGenerateUpdateSQL(poFeature);

            /* Prepare the SQL into a statement */
            int err = sqlite3_prepare_v2(m_poDS->GetDB(), osCommand, -1, &m_poUpdateStatement, NULL);
            if ( err != SQLITE_OK )
            {
                CPLError( CE_Failure, CPLE_AppDefined,
                        "failed to prepare SQL: %s", osCommand.c_str());
                return OGRERR_FAILURE;
            }
        }

        /* Bind values onto the statement now */
        OGRErr errOgr = FeatureBindUpdateParameters(poFeature, m_poUpdateStatement);
        if ( errOgr != OGRERR_NONE )
        {
            sqlite3_reset(m_poUpdateStatement);
            sqlite3_clear_bindings(m_poUpdateStatement);
            return errOgr;
        }
    }

    /* From here execute the statement and check errors */
    int err = sqlite3_step(m_poUpdateStatement);
    if ( ! (err == SQLITE_OK || err == SQLITE_DONE) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "failed to execute update : %s",
                  sqlite3_errmsg( m_poDS->GetDB() ) );
        sqlite3_reset(m_poUpdateStatement);
        sqlite3_clear_bindings(m_poUpdateStatement);
        return OGRERR_FAILURE;
    }

    sqlite3_reset(m_poUpdateStatement);
    sqlite3_clear_bindings(m_poUpdateStatement);

    /* Only update the envelope if we changed something */
    OGRErr eErr = (sqlite3_changes(m_poDS->GetDB()) > 0) ? OGRERR_NONE : OGRERR_NON_EXISTING_FEATURE;
    if (eErr == OGRERR_NONE)
    {
        /* Update the layer extents with this new object */
        if ( IsGeomFieldSet(poFeature) )
        {
            OGREnvelope oEnv;
            poFeature->GetGeomFieldRef(0)->getEnvelope(&oEnv);
            UpdateExtent(&oEnv);
        }
    }

    /* All done! */
    return eErr;
}



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

OGRErr OGRGeoPackageTableLayer::SetAttributeFilter( const char *pszQuery )

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

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

    BuildWhere();

    ResetReading();

    return OGRERR_NONE;
}


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

void OGRGeoPackageTableLayer::ResetReading()
{
    if( m_bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE )
        return;

    OGRGeoPackageLayer::ResetReading();

    if ( m_poInsertStatement )
    {
        sqlite3_finalize(m_poInsertStatement);
        m_poInsertStatement = NULL;
    }

    if ( m_poUpdateStatement )
    {
        sqlite3_finalize(m_poUpdateStatement);
        m_poUpdateStatement = NULL;
    }

    BuildColumns();
    return;
}


/************************************************************************/
/*                           ResetStatement()                           */
/************************************************************************/

OGRErr OGRGeoPackageTableLayer::ResetStatement()

{
    ClearStatement();

    /* There is no active query statement set up, */
    /* so job #1 is to prepare the statement. */
    /* Append the attribute filter, if there is one */
    CPLString soSQL;
    if ( m_soFilter.length() > 0 )
        soSQL.Printf("SELECT %s FROM \"%s\" WHERE %s", m_soColumns.c_str(), m_pszTableName, m_soFilter.c_str());
    else
        soSQL.Printf("SELECT %s FROM \"%s\" ", m_soColumns.c_str(), m_pszTableName);

    int err = sqlite3_prepare(m_poDS->GetDB(), soSQL.c_str(), -1, &m_poQueryStatement, NULL);
    if ( err != SQLITE_OK )
    {
        m_poQueryStatement = NULL;
        CPLError( CE_Failure, CPLE_AppDefined, "failed to prepare SQL: %s", soSQL.c_str());
        return OGRERR_FAILURE;
    }

    return OGRERR_NONE;
}

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

OGRFeature* OGRGeoPackageTableLayer::GetNextFeature()
{
    if( m_bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE )
        return NULL;

    CreateSpatialIndexIfNecessary();

    OGRFeature* poFeature = OGRGeoPackageLayer::GetNextFeature();
    if( poFeature && m_iFIDAsRegularColumnIndex >= 0 )
    {
        poFeature->SetField(m_iFIDAsRegularColumnIndex, poFeature->GetFID());
    }
    return poFeature;
}

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

OGRFeature* OGRGeoPackageTableLayer::GetFeature(GIntBig nFID)
{
    /* No FID, no answer. */
    if (nFID == OGRNullFID || m_pszFidColumn == NULL )
        return NULL;

    if( m_bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE )
        return NULL;

    CreateSpatialIndexIfNecessary();

    /* Clear out any existing query */
    ResetReading();

    /* No filters apply, just use the FID */
    CPLString soSQL;
    soSQL.Printf("SELECT %s FROM \"%s\" WHERE \"%s\" = " CPL_FRMT_GIB,
                 m_soColumns.c_str(), m_pszTableName, m_pszFidColumn, nFID);

    int err = sqlite3_prepare(m_poDS->GetDB(), soSQL.c_str(), -1, &m_poQueryStatement, NULL);
    if ( err != SQLITE_OK )
    {
        m_poQueryStatement = NULL;
        CPLError( CE_Failure, CPLE_AppDefined, "failed to prepare SQL: %s", soSQL.c_str());
        return NULL;
    }

    /* Should be only one or zero results */
    err = sqlite3_step(m_poQueryStatement);

    /* Nothing left in statement? NULL return indicates to caller */
    /* that there are no features left */
    if ( err == SQLITE_DONE )
        return NULL;

    /* Aha, got one */
    if ( err == SQLITE_ROW )
    {
        OGRFeature* poFeature = TranslateFeature(m_poQueryStatement);
        if( poFeature && m_iFIDAsRegularColumnIndex >= 0 )
        {
            poFeature->SetField(m_iFIDAsRegularColumnIndex, poFeature->GetFID());
        }
        return poFeature;
    }

    /* Error out on all other return codes */
    return NULL;
}

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

OGRErr OGRGeoPackageTableLayer::DeleteFeature(GIntBig nFID)
{
    if( !m_poDS->GetUpdate() || m_pszFidColumn == NULL )
    {
        return OGRERR_FAILURE;
    }

    /* No FID, no answer. */
    if (nFID == OGRNullFID)
    {
        CPLError( CE_Failure, CPLE_AppDefined, "delete feature called with null FID");
        return OGRERR_FAILURE;
    }

    if( m_bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE )
        return OGRERR_FAILURE;

    /* Clear out any existing query */
    ResetReading();

    /* No filters apply, just use the FID */
    CPLString soSQL;
    soSQL.Printf("DELETE FROM \"%s\" WHERE \"%s\" = " CPL_FRMT_GIB,
                 m_pszTableName, m_pszFidColumn, nFID);

    OGRErr eErr = SQLCommand(m_poDS->GetDB(), soSQL.c_str());
    if( eErr == OGRERR_NONE )
        eErr = (sqlite3_changes(m_poDS->GetDB()) > 0) ? OGRERR_NONE : OGRERR_NON_EXISTING_FEATURE;
    return eErr;
}

/************************************************************************/
/*                        SyncToDisk()                                  */
/************************************************************************/

OGRErr OGRGeoPackageTableLayer::SyncToDisk()
{
    if( m_bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE )
        return OGRERR_FAILURE;

    SaveExtent();
    return OGRERR_NONE;
}

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

OGRErr OGRGeoPackageTableLayer::StartTransaction()
{
    return m_poDS->StartTransaction();
}


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

OGRErr OGRGeoPackageTableLayer::CommitTransaction()
{
    return m_poDS->CommitTransaction();
}


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

OGRErr OGRGeoPackageTableLayer::RollbackTransaction()
{
    return m_poDS->RollbackTransaction();
}


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

GIntBig OGRGeoPackageTableLayer::GetFeatureCount( CPL_UNUSED int bForce )
{
    if( m_poFilterGeom != NULL && !m_bFilterIsEnvelope )
        return OGRGeoPackageLayer::GetFeatureCount();

    if( m_bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE )
        return 0;

    /* Ignore bForce, because we always do a full count on the database */
    OGRErr err;
    CPLString soSQL;
    if ( m_soFilter.length() > 0 )
        soSQL.Printf("SELECT Count(*) FROM \"%s\" WHERE %s", m_pszTableName, m_soFilter.c_str());
    else
        soSQL.Printf("SELECT Count(*) FROM \"%s\" ", m_pszTableName);

    /* Just run the query directly and get back integer */
    GIntBig iFeatureCount = SQLGetInteger64(m_poDS->GetDB(), soSQL.c_str(), &err);

    /* Generic implementation uses -1 for error condition, so we will too */
    if ( err == OGRERR_NONE )
        return iFeatureCount;
    else
        return -1;
}


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

OGRErr OGRGeoPackageTableLayer::GetExtent(OGREnvelope *psExtent, int bForce)
{
    /* Extent already calculated! We're done. */
    if ( m_poExtent != NULL )
    {
        if ( psExtent )
        {
            *psExtent = *m_poExtent;
        }
        return OGRERR_NONE;
    }

    if( m_bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE )
        return OGRERR_FAILURE;

    /* User is OK with expensive calculation, fall back to */
    /* default implementation (scan all features) and save */
    /* the result for later */
    if ( bForce )
    {
        OGRErr err = OGRLayer::GetExtent(psExtent, bForce);
        if ( err != OGRERR_NONE )
            return err;

        if ( ! m_poExtent )
            m_poExtent = new OGREnvelope( *psExtent );
        else
            *m_poExtent = *psExtent;
        return SaveExtent();
    }

    return OGRERR_FAILURE;
}



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

int OGRGeoPackageTableLayer::TestCapability ( const char * pszCap )
{
    if ( EQUAL(pszCap, OLCCreateField) ||
         EQUAL(pszCap, OLCSequentialWrite) ||
         EQUAL(pszCap, OLCDeleteFeature) ||
         EQUAL(pszCap, OLCRandomWrite) )
    {
        return m_poDS->GetUpdate();
    }
    else if ( EQUAL(pszCap, OLCRandomRead) ||
              EQUAL(pszCap, OLCTransactions) )
    {
        return TRUE;
    }
    else if ( EQUAL(pszCap, OLCFastSpatialFilter) )
    {
        return HasSpatialIndex();
    }
    else if ( EQUAL(pszCap, OLCFastGetExtent) )
    {
        if ( m_poExtent )
            return TRUE;
        else
            return FALSE;
    }
    else if( EQUAL(pszCap,OLCCurveGeometries) )
        return TRUE;
    else if( EQUAL(pszCap,OLCMeasuredGeometries) )
        return TRUE;
    else
    {
        return OGRGeoPackageLayer::TestCapability(pszCap);
    }
}

/************************************************************************/
/*                     CreateSpatialIndexIfNecessary()                  */
/************************************************************************/

void OGRGeoPackageTableLayer::CreateSpatialIndexIfNecessary()
{
    if( m_bDeferredSpatialIndexCreation )
    {
        CreateSpatialIndex();
    }
}

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

bool OGRGeoPackageTableLayer::CreateSpatialIndex()
{
    char* pszSQL;
    OGRErr err;

    if( m_bDeferredCreation && RunDeferredCreationIfNecessary() != OGRERR_NONE )
        return false;

    m_bDeferredSpatialIndexCreation = false;

    if( m_pszFidColumn == NULL )
        return false;

    if( HasSpatialIndex() )
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Spatial index already existing");
        return false;
    }

    if( m_poFeatureDefn->GetGeomFieldCount() == 0 )
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Spatial index already existing");
        return false;
    }
    if( m_poDS->CreateExtensionsTableIfNecessary() != OGRERR_NONE )
        return false;

    const char* pszT = m_pszTableName;
    const char* pszC = m_poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef();
    const char* pszI = GetFIDColumn();

    m_poDS->SoftStartTransaction();

    /* Register the table in gpkg_extensions */
    pszSQL = sqlite3_mprintf(
                 "INSERT INTO gpkg_extensions "
                 "(table_name,column_name,extension_name,definition,scope) "
                 "VALUES ('%q', '%q', 'gpkg_rtree_index', 'GeoPackage 1.0 Specification Annex L', 'write-only')",
                 pszT, pszC );
    err = SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);
    if( err != OGRERR_NONE )
    {
        m_poDS->SoftRollbackTransaction();
        return false;
    }

    /* Create virtual table */
    if( !m_bDropRTreeTable )
    {
        pszSQL = sqlite3_mprintf(
                    "CREATE VIRTUAL TABLE \"rtree_%s_%s\" USING rtree(id, minx, maxx, miny, maxy)",
                    pszT, pszC );
        err = SQLCommand(m_poDS->GetDB(), pszSQL);
        sqlite3_free(pszSQL);
        if( err != OGRERR_NONE )
        {
            m_poDS->SoftRollbackTransaction();
            return false;
        }
    }
    m_bDropRTreeTable = false;

    /* Populate the RTree */
    pszSQL = sqlite3_mprintf(
                 "INSERT OR REPLACE INTO \"rtree_%s_%s\" "
                 "SELECT \"%s\", st_minx(\"%s\"), st_maxx(\"%s\"), st_miny(\"%s\"), st_maxy(\"%s\") FROM \"%s\"",
                 pszT, pszC, pszI, pszC, pszC, pszC, pszC, pszT );
    err = SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);
    if( err != OGRERR_NONE )
    {
        m_poDS->SoftRollbackTransaction();
        return false;
    }

    /* Define Triggers to Maintain Spatial Index Values */

    /* Conditions: Insertion of non-empty geometry
       Actions   : Insert record into rtree */
    pszSQL = sqlite3_mprintf(
                   "CREATE TRIGGER \"rtree_%s_%s_insert\" AFTER INSERT ON \"%s\" "
                   "WHEN (new.\"%s\" NOT NULL AND NOT ST_IsEmpty(NEW.\"%s\")) "
                   "BEGIN "
                   "INSERT OR REPLACE INTO \"rtree_%s_%s\" VALUES ("
                   "NEW.\"%s\","
                   "ST_MinX(NEW.\"%s\"), ST_MaxX(NEW.\"%s\"),"
                   "ST_MinY(NEW.\"%s\"), ST_MaxY(NEW.\"%s\")"
                   "); "
                   "END",
                   pszT, pszC, pszT,
                   pszC, pszC,
                   pszT, pszC,
                   pszI,
                   pszC, pszC,
                   pszC, pszC);
    err = SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);
    if( err != OGRERR_NONE )
    {
        m_poDS->SoftRollbackTransaction();
        return false;
    }

    /* Conditions: Update of geometry column to non-empty geometry
               No row ID change
       Actions   : Update record in rtree */
    pszSQL = sqlite3_mprintf(
                   "CREATE TRIGGER \"rtree_%s_%s_update1\" AFTER UPDATE OF \"%s\" ON \"%s\" "
                   "WHEN OLD.\"%s\" = NEW.\"%s\" AND "
                   "(NEW.\"%s\" NOTNULL AND NOT ST_IsEmpty(NEW.\"%s\")) "
                   "BEGIN "
                   "INSERT OR REPLACE INTO \"rtree_%s_%s\" VALUES ("
                   "NEW.\"%s\","
                   "ST_MinX(NEW.\"%s\"), ST_MaxX(NEW.\"%s\"),"
                   "ST_MinY(NEW.\"%s\"), ST_MaxY(NEW.\"%s\")"
                   "); "
                   "END",
                   pszT, pszC, pszC, pszT,
                   pszI, pszI,
                   pszC, pszC,
                   pszT, pszC,
                   pszI,
                   pszC, pszC,
                   pszC, pszC);
    err = SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);
    if( err != OGRERR_NONE )
    {
        m_poDS->SoftRollbackTransaction();
        return false;
    }

    /* Conditions: Update of geometry column to empty geometry
               No row ID change
       Actions   : Remove record from rtree */
    pszSQL = sqlite3_mprintf(
                   "CREATE TRIGGER \"rtree_%s_%s_update2\" AFTER UPDATE OF \"%s\" ON \"%s\" "
                   "WHEN OLD.\"%s\" = NEW.\"%s\" AND "
                   "(NEW.\"%s\" ISNULL OR ST_IsEmpty(NEW.\"%s\")) "
                   "BEGIN "
                   "DELETE FROM \"rtree_%s_%s\" WHERE id = OLD.\"%s\"; "
                   "END",
                   pszT, pszC, pszC, pszT,
                   pszI, pszI,
                   pszC, pszC,
                   pszT, pszC, pszI);
    err = SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);
    if( err != OGRERR_NONE )
    {
        m_poDS->SoftRollbackTransaction();
        return false;
    }

    /* Conditions: Update of any column
                    Row ID change
                    Non-empty geometry
        Actions   : Remove record from rtree for old <i>
                    Insert record into rtree for new <i> */
    pszSQL = sqlite3_mprintf(
                   "CREATE TRIGGER \"rtree_%s_%s_update3\" AFTER UPDATE OF \"%s\" ON \"%s\" "
                   "WHEN OLD.\"%s\" != NEW.\"%s\" AND "
                   "(NEW.\"%s\" NOTNULL AND NOT ST_IsEmpty(NEW.\"%s\")) "
                   "BEGIN "
                   "DELETE FROM \"rtree_%s_%s\" WHERE id = OLD.\"%s\"; "
                   "INSERT OR REPLACE INTO \"rtree_%s_%s\" VALUES ("
                   "NEW.\"%s\","
                   "ST_MinX(NEW.\"%s\"), ST_MaxX(NEW.\"%s\"),"
                   "ST_MinY(NEW.\"%s\"), ST_MaxY(NEW.\"%s\")"
                   "); "
                   "END",
                   pszT, pszC, pszC, pszT,
                   pszI, pszI,
                   pszC, pszC,
                   pszT, pszC, pszI,
                   pszT, pszC,
                   pszI,
                   pszC, pszC,
                   pszC, pszC);
    err = SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);
    if( err != OGRERR_NONE )
    {
        m_poDS->SoftRollbackTransaction();
        return false;
    }

    /* Conditions: Update of any column
                    Row ID change
                    Empty geometry
        Actions   : Remove record from rtree for old and new <i> */
    pszSQL = sqlite3_mprintf(
                   "CREATE TRIGGER \"rtree_%s_%s_update4\" AFTER UPDATE ON \"%s\" "
                   "WHEN OLD.\"%s\" != NEW.\"%s\" AND "
                   "(NEW.\"%s\" ISNULL OR ST_IsEmpty(NEW.\"%s\")) "
                   "BEGIN "
                   "DELETE FROM \"rtree_%s_%s\" WHERE id IN (OLD.\"%s\", NEW.\"%s\"); "
                   "END",
                   pszT, pszC, pszT,
                   pszI, pszI,
                   pszC, pszC,
                   pszT, pszC, pszI, pszI);
    err = SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);
    if( err != OGRERR_NONE )
    {
        m_poDS->SoftRollbackTransaction();
        return false;
    }

    /* Conditions: Row deleted
        Actions   : Remove record from rtree for old <i> */
    pszSQL = sqlite3_mprintf(
                   "CREATE TRIGGER \"rtree_%s_%s_delete\" AFTER DELETE ON \"%s\" "
                   "WHEN old.\"%s\" NOT NULL "
                   "BEGIN "
                   "DELETE FROM \"rtree_%s_%s\" WHERE id = OLD.\"%s\"; "
                   "END",
                   pszT, pszC, pszT,
                   pszC,
                   pszT, pszC, pszI);
    err = SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);
    if( err != OGRERR_NONE )
    {
        m_poDS->SoftRollbackTransaction();
        return false;
    }

    m_poDS->SoftCommitTransaction();

    m_bHasSpatialIndex = true;

    return true;
}

/************************************************************************/
/*                    CheckUnknownExtensions()                          */
/************************************************************************/

void OGRGeoPackageTableLayer::CheckUnknownExtensions()
{
    if( !m_poDS->HasExtensionsTable() )
        return;

    const char* pszT = m_pszTableName;

    /* We have only the SQL functions needed by the 3 following extensions */
    /* anything else will likely cause troubles */
    char* pszSQL;

    if( m_poFeatureDefn->GetGeomFieldCount() == 0 )
    {
        pszSQL = sqlite3_mprintf(
                    "SELECT extension_name, definition, scope FROM gpkg_extensions WHERE table_name='%q'"
#ifdef WORKAROUND_SQLITE3_BUGS
                    " OR 0"
#endif
                    ,pszT );
    }
    else
    {
        pszSQL = sqlite3_mprintf(
                    "SELECT extension_name, definition, scope FROM gpkg_extensions WHERE (table_name='%q' "
                    "AND column_name='%q' AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
                    "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', 'gpkg_geom_MULTICURVE', "
                    "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
                    "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', 'gpkg_srs_id_trigger'))"
#ifdef WORKAROUND_SQLITE3_BUGS
                    " OR 0"
#endif
                    ,pszT,
                    m_poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef() );
    }
    SQLResult oResultTable;
    OGRErr err = SQLQuery(m_poDS->GetDB(), pszSQL, &oResultTable);
    sqlite3_free(pszSQL);
    if ( err == OGRERR_NONE && oResultTable.nRowCount > 0 )
    {
        for(int i=0; i<oResultTable.nRowCount;i++)
        {
            const char* pszExtName = SQLResultGetValue(&oResultTable, 0, i);
            const char* pszDefinition = SQLResultGetValue(&oResultTable, 1, i);
            const char* pszScope = SQLResultGetValue(&oResultTable, 2, i);
            if( pszExtName == NULL ) pszExtName = "(null)";
            if( pszDefinition == NULL ) pszDefinition = "(null)";
            if( pszScope == NULL ) pszScope = "(null)";
            if( m_poDS->GetUpdate() && EQUAL(pszScope, "write-only") )
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "Layer %s relies on the '%s' (%s) extension that should "
                         "be implemented for safe write-support, but is not currently. "
                         "Update of that layer are strongly discouraged to avoid corruption.",
                         GetName(), pszExtName, pszDefinition);
            }
            else if( m_poDS->GetUpdate() && EQUAL(pszScope, "read-write") )
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "Layer %s relies on the '%s' (%s) extension that should "
                         "be implemented in order to read/write it safely, but is not currently. "
                         "Some data may be missing while reading that layer, and updates are strongly discouraged.",
                         GetName(), pszExtName, pszDefinition);
            }
            else if( EQUAL(pszScope, "read-write") )
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "Layer %s relies on the '%s' (%s) extension that should "
                         "be implemented in order to read it safely, but is not currently. "
                         "Some data may be missing while reading that layer.",
                         GetName(), pszExtName, pszDefinition);
            }
        }
    }
    SQLResultFree(&oResultTable);
}

/************************************************************************/
/*                     CreateGeometryExtensionIfNecessary()             */
/************************************************************************/

bool OGRGeoPackageTableLayer::CreateGeometryExtensionIfNecessary(OGRwkbGeometryType eGType)
{
    eGType = wkbFlatten(eGType);
    CPLAssert(eGType <= wkbTIN);
    if( m_abHasGeometryExtension[eGType] )
        return true;

    if( m_poDS->CreateExtensionsTableIfNecessary() != OGRERR_NONE )
        return false;

    const char* pszT = m_pszTableName;
    const char* pszC = m_poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef();
    const char *pszGeometryType = m_poDS->GetGeometryTypeString(eGType);

    // Check first if the extension isn't registered
    char* pszSQL = sqlite3_mprintf(
        "SELECT 1 FROM gpkg_extensions WHERE table_name = '%q' AND "
        "column_name = '%q' AND extension_name = 'gpkg_geom_%s'",
         pszT, pszC, pszGeometryType);
    OGRErr err = OGRERR_NONE;
    SQLGetInteger(m_poDS->GetDB(), pszSQL, &err);
    sqlite3_free(pszSQL);

    if( err != OGRERR_NONE )
    {
        /* Register the table in gpkg_extensions */
        pszSQL = sqlite3_mprintf(
                    "INSERT INTO gpkg_extensions "
                    "(table_name,column_name,extension_name,definition,scope) "
                    "VALUES ('%q', '%q', 'gpkg_geom_%s', 'GeoPackage 1.0 Specification Annex J', 'read-write')",
                    pszT, pszC, pszGeometryType);
        err = SQLCommand(m_poDS->GetDB(), pszSQL);
        sqlite3_free(pszSQL);
        if ( err != OGRERR_NONE )
            return false;
    }

    m_abHasGeometryExtension[eGType] = true;
    return true;
}

/************************************************************************/
/*                        HasSpatialIndex()                             */
/************************************************************************/

bool OGRGeoPackageTableLayer::HasSpatialIndex()
{
    if( m_bHasSpatialIndex >= 0 )
        return CPL_TO_BOOL(m_bHasSpatialIndex);
    m_bHasSpatialIndex = false;

    if( m_poFeatureDefn->GetGeomFieldCount() == 0 ||
        !m_poDS->HasExtensionsTable() )
        return false;

    const char* pszT = m_pszTableName;
    const char* pszC = m_poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef();

    /* Check into gpkg_extensions */
    char* pszSQL = sqlite3_mprintf(
                 "SELECT * FROM gpkg_extensions WHERE (table_name='%q' "
                 "AND column_name='%q' AND extension_name='gpkg_rtree_index')"
#ifdef WORKAROUND_SQLITE3_BUGS
                " OR 0"
#endif
                 ,pszT, pszC );
    SQLResult oResultTable;
    OGRErr err = SQLQuery(m_poDS->GetDB(), pszSQL, &oResultTable);
    sqlite3_free(pszSQL);
    if ( err == OGRERR_NONE && oResultTable.nRowCount == 1 )
    {
        m_bHasSpatialIndex = true;
    }
    SQLResultFree(&oResultTable);

    return CPL_TO_BOOL(m_bHasSpatialIndex);
}

/************************************************************************/
/*                        DropSpatialIndex()                            */
/************************************************************************/

bool OGRGeoPackageTableLayer::DropSpatialIndex(bool bCalledFromSQLFunction)
{
    if( !HasSpatialIndex() )
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Spatial index not existing");
        return false;
    }

    const char* pszT = m_pszTableName;
    const char* pszC =m_poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef();
    char* pszSQL;

    pszSQL = sqlite3_mprintf("DELETE FROM gpkg_extensions WHERE table_name='%q' "
                 "AND column_name='%q' AND extension_name='gpkg_rtree_index'",
                 pszT, pszC );
    SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);

    if( bCalledFromSQLFunction )
    {
        /* We cannot drop a table from a SQLite function call, so we just */
        /* remove the content and memorize that we will have to delete the */
        /* table later */
        m_bDropRTreeTable = true;
        pszSQL = sqlite3_mprintf("DELETE FROM \"rtree_%s_%s\"", pszT, pszC);
    }
    else
    {
        pszSQL = sqlite3_mprintf("DROP TABLE \"rtree_%s_%s\"", pszT, pszC);
    }
    SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);

    pszSQL = sqlite3_mprintf("DROP TRIGGER \"rtree_%s_%s_insert\"", pszT, pszC);
    SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);

    pszSQL = sqlite3_mprintf("DROP TRIGGER \"rtree_%s_%s_update1\"", pszT, pszC);
    SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);

    pszSQL = sqlite3_mprintf("DROP TRIGGER \"rtree_%s_%s_update2\"", pszT, pszC);
    SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);

    pszSQL = sqlite3_mprintf("DROP TRIGGER \"rtree_%s_%s_update3\"", pszT, pszC);
    SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);

    pszSQL = sqlite3_mprintf("DROP TRIGGER \"rtree_%s_%s_update4\"", pszT, pszC);
    SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);

    pszSQL = sqlite3_mprintf("DROP TRIGGER \"rtree_%s_%s_delete\"", pszT, pszC);
    SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);

    m_bHasSpatialIndex = false;
    return true;
}

/************************************************************************/
/*                          RenameTo()                                  */
/************************************************************************/

void OGRGeoPackageTableLayer::RenameTo(const char* pszDstTableName)
{
    int bHasSpatialIndex = HasSpatialIndex();

    if( bHasSpatialIndex )
    {
        DropSpatialIndex();
    }

    /* We also need to update GeoPackage metadata tables */
    char* pszSQL;
    pszSQL = sqlite3_mprintf(
            "UPDATE gpkg_geometry_columns SET table_name = '%s' WHERE table_name = '%s'",
            pszDstTableName, m_pszTableName);
    SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);

    pszSQL = sqlite3_mprintf(
            "UPDATE gpkg_contents SET table_name = '%s' WHERE table_name = '%s'",
            pszDstTableName, m_pszTableName);
    SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);

    CPLFree(m_pszTableName);
    m_pszTableName = CPLStrdup(pszDstTableName);

    if( bHasSpatialIndex )
    {
        CreateSpatialIndex();
    }
}

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

void OGRGeoPackageTableLayer::SetSpatialFilter( OGRGeometry * poGeomIn )

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

        ResetReading();
    }
}

/************************************************************************/
/*                        HasFastSpatialFilter()                        */
/************************************************************************/

int OGRGeoPackageTableLayer::HasFastSpatialFilter(int iGeomColIn)
{
    if( iGeomColIn < 0 || iGeomColIn >= m_poFeatureDefn->GetGeomFieldCount() )
        return FALSE;
    return HasSpatialIndex();
}

/************************************************************************/
/*                           GetSpatialWhere()                          */
/************************************************************************/

CPLString OGRGeoPackageTableLayer::GetSpatialWhere(int iGeomColIn,
                                               OGRGeometry* poFilterGeom)
{
    CPLString osSpatialWHERE;

    if( iGeomColIn < 0 || iGeomColIn >= m_poFeatureDefn->GetGeomFieldCount() )
        return osSpatialWHERE;

    const char* pszT = m_pszTableName;
    const char* pszC = m_poFeatureDefn->GetGeomFieldDefn(iGeomColIn)->GetNameRef();

    if( poFilterGeom != NULL )
    {
        OGREnvelope  sEnvelope;

        poFilterGeom->getEnvelope( &sEnvelope );

        if( CPLIsInf(sEnvelope.MinX) || CPLIsInf(sEnvelope.MinY) ||
            CPLIsInf(sEnvelope.MaxX) || CPLIsInf(sEnvelope.MaxY) )
        {
            return osSpatialWHERE;
        }

        if( HasSpatialIndex() )
        {
            osSpatialWHERE.Printf("ROWID IN ( SELECT id FROM \"rtree_%s_%s\" WHERE "
                            "maxx >= %.12f AND minx <= %.12f AND maxy >= %.12f AND miny <= %.12f)",
                            pszT, pszC,
                            sEnvelope.MinX - 1e-11, sEnvelope.MaxX + 1e-11,
                            sEnvelope.MinY - 1e-11, sEnvelope.MaxY + 1e-11);
        }
        else
        {
            /* A bit inefficient but still faster than OGR filtering */
            osSpatialWHERE.Printf(
                        "(ST_MaxX(\"%s\") >= %.12f AND ST_MinX(\"%s\") <= %.12f AND "
                        "ST_MaxY(\"%s\") >= %.12f AND ST_MinY(\"%s\") <= %.12f)",
                        pszC, sEnvelope.MinX - 1e-11,
                        pszC, sEnvelope.MaxX + 1e-11,
                        pszC, sEnvelope.MinY - 1e-11,
                        pszC, sEnvelope.MaxY + 1e-11);
        }
    }

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

void OGRGeoPackageTableLayer::BuildWhere()

{
    m_soFilter = "";

    CPLString osSpatialWHERE = GetSpatialWhere(m_iGeomFieldFilter,
                                               m_poFilterGeom);
    if (osSpatialWHERE.size() != 0)
    {
        m_soFilter += osSpatialWHERE;
    }

    if( osQuery.size() > 0 )
    {
        if( m_soFilter.size() == 0 )
        {
            m_soFilter += osQuery;
        }
        else
        {
            m_soFilter += " AND (";
            m_soFilter += osQuery;
            m_soFilter += ")";
        }
    }
}

/************************************************************************/
/*                        SetCreationParameters()                       */
/************************************************************************/

void OGRGeoPackageTableLayer::SetCreationParameters( OGRwkbGeometryType eGType,
                                                     const char* pszGeomColumnName,
                                                     int bGeomNullable,
                                                     OGRSpatialReference* poSRS,
                                                     const char* pszFIDColumnName,
                                                     const char* pszIdentifier,
                                                     const char* pszDescription )
{
    m_bDeferredCreation = true;
    m_pszFidColumn = CPLStrdup(pszFIDColumnName);
    m_poFeatureDefn = new OGRFeatureDefn( m_pszTableName );
    SetDescription( m_poFeatureDefn->GetName() );
    m_poFeatureDefn->SetGeomType(wkbNone);
    m_poFeatureDefn->Reference();
    if( eGType != wkbNone )
    {
        OGRGeomFieldDefn oGeomFieldDefn(pszGeomColumnName, eGType);
        if( poSRS )
            m_iSrs = m_poDS->GetSrsId(poSRS);
        oGeomFieldDefn.SetSpatialRef(poSRS);
        oGeomFieldDefn.SetNullable(bGeomNullable);
        m_poFeatureDefn->AddGeomFieldDefn(&oGeomFieldDefn);
    }
    if( pszIdentifier )
    {
        m_osIdentifierLCO = pszIdentifier;
        OGRLayer::SetMetadataItem("IDENTIFIER", pszIdentifier);
    }
    if( pszDescription )
    {
        m_osDescriptionLCO = pszDescription;
        OGRLayer::SetMetadataItem("DESCRIPTION", pszDescription);
    }
}

/************************************************************************/
/*                      RegisterGeometryColumn()                        */
/************************************************************************/

OGRErr OGRGeoPackageTableLayer::RegisterGeometryColumn()
{
    OGRwkbGeometryType eGType = GetGeomType();
    const char *pszGeometryType = m_poDS->GetGeometryTypeString(eGType);
    /* Requirement 27: The z value in a gpkg_geometry_columns table row */
    /* SHALL be one of 0 (none), 1 (mandatory), or 2 (optional) */
    bool bGeometryTypeHasZ = CPL_TO_BOOL(wkbHasZ(eGType));
    bool bGeometryTypeHasM = CPL_TO_BOOL(wkbHasM(eGType));

    /* Update gpkg_geometry_columns with the table info */
    char* pszSQL = sqlite3_mprintf(
        "INSERT INTO gpkg_geometry_columns "
        "(table_name,column_name,geometry_type_name,srs_id,z,m)"
        " VALUES "
        "('%q','%q','%q',%d,%d,%d)",
        GetName(),GetGeometryColumn(),pszGeometryType,
        m_iSrs,static_cast<int>(bGeometryTypeHasZ),
        static_cast<int>(bGeometryTypeHasM));

    OGRErr err = SQLCommand(m_poDS->GetDB(), pszSQL);
    sqlite3_free(pszSQL);
    if ( err != OGRERR_NONE )
        return OGRERR_FAILURE;

    if( OGR_GT_IsNonLinear( eGType ) || wkbFlatten(eGType) == wkbCurve ||
        wkbFlatten(eGType) == wkbSurface )
    {
        CreateGeometryExtensionIfNecessary(eGType);
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                      RunDeferredCreationIfNecessary()                */
/************************************************************************/

OGRErr OGRGeoPackageTableLayer::RunDeferredCreationIfNecessary()
{
    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 += ")";

#ifdef DEBUG
    CPLDebug( "GPKG", "exec(%s)", osCommand.c_str() );
#endif
    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();

    return OGRERR_NONE;
}

/************************************************************************/
/*                            GetMetadata()                             */
/************************************************************************/

char **OGRGeoPackageTableLayer::GetMetadata( const char *pszDomain )

{
    if( m_bHasReadMetadataFromStorage )
        return OGRLayer::GetMetadata( pszDomain );

    m_bHasReadMetadataFromStorage = true;

    if ( !m_poDS->HasMetadataTables() )
        return OGRLayer::GetMetadata( pszDomain );

    char* pszSQL;

    pszSQL = sqlite3_mprintf(
        "SELECT md.metadata, md.md_standard_uri, md.mime_type, mdr.reference_scope FROM gpkg_metadata md "
        "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
        "WHERE mdr.table_name = '%q' ORDER BY md.id",
        m_pszTableName);


    SQLResult oResult;
    OGRErr err = SQLQuery(m_poDS->GetDB(), pszSQL, &oResult);
    sqlite3_free(pszSQL);
    if  ( err != OGRERR_NONE )
    {
        SQLResultFree(&oResult);
        return OGRLayer::GetMetadata( pszDomain );
    }

    char** papszMetadata = CSLDuplicate(OGRLayer::GetMetadata());

    /* GDAL metadata */
    for(int i=0;i<oResult.nRowCount;i++)
    {
        const char *pszMetadata = SQLResultGetValue(&oResult, 0, i);
        const char* pszMDStandardURI = SQLResultGetValue(&oResult, 1, i);
        const char* pszMimeType = SQLResultGetValue(&oResult, 2, i);
        //const char* pszReferenceScope = SQLResultGetValue(&oResult, 3, i);
        //int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
        if( pszMetadata == NULL )
            continue;
        if( pszMDStandardURI != NULL && EQUAL(pszMDStandardURI, "http://gdal.org") &&
            pszMimeType != NULL && EQUAL(pszMimeType, "text/xml") )
        {
            CPLXMLNode* psXMLNode = CPLParseXMLString(pszMetadata);
            if( psXMLNode )
            {
                GDALMultiDomainMetadata oLocalMDMD;
                oLocalMDMD.XMLInit(psXMLNode, FALSE);

                papszMetadata = CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
                char** papszDomainList = oLocalMDMD.GetDomainList();
                char** papszIter = papszDomainList;
                while( papszIter && *papszIter )
                {
                    if( !EQUAL(*papszIter, "") )
                        oMDMD.SetMetadata(oLocalMDMD.GetMetadata(*papszIter), *papszIter);
                    papszIter ++;
                }

                CPLDestroyXMLNode(psXMLNode);
            }
        }
    }

    OGRLayer::SetMetadata(papszMetadata);
    CSLDestroy(papszMetadata);
    papszMetadata = NULL;

    /* Add non-GDAL metadata now */
    int nNonGDALMDILocal = 1;
    for(int i=0;i<oResult.nRowCount;i++)
    {
        const char *pszMetadata = SQLResultGetValue(&oResult, 0, i);
        const char* pszMDStandardURI = SQLResultGetValue(&oResult, 1, i);
        const char* pszMimeType = SQLResultGetValue(&oResult, 2, i);
        //const char* pszReferenceScope = SQLResultGetValue(&oResult, 3, i);
        //int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
        if( pszMetadata == NULL )
            continue;
        if( pszMDStandardURI != NULL && EQUAL(pszMDStandardURI, "http://gdal.org") &&
            pszMimeType != NULL && EQUAL(pszMimeType, "text/xml") )
            continue;

        /*if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd" ) == 0 &&
            strcmp( pszMimeType, "text/xml" ) == 0 )
        {
            char* apszMD[2];
            apszMD[0] = (char*)pszMetadata;
            apszMD[1] = NULL;
            oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
        }
        else*/
        {
            oMDMD.SetMetadataItem( CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
                                    pszMetadata );
            nNonGDALMDILocal ++;
        }
    }

    SQLResultFree(&oResult);

    return OGRLayer::GetMetadata(pszDomain);
}

/************************************************************************/
/*                          GetMetadataItem()                           */
/************************************************************************/

const char *OGRGeoPackageTableLayer::GetMetadataItem( const char * pszName,
                                                    const char * pszDomain )
{
    return CSLFetchNameValue( GetMetadata(pszDomain), pszName );
}

/************************************************************************/
/*                      GetMetadataDomainList()                         */
/************************************************************************/

char **OGRGeoPackageTableLayer::GetMetadataDomainList()
{
    GetMetadata();
    return OGRLayer::GetMetadataDomainList();
}

/************************************************************************/
/*                            SetMetadata()                             */
/************************************************************************/

CPLErr OGRGeoPackageTableLayer::SetMetadata( char ** papszMetadata, const char * pszDomain )
{
    GetMetadata(); /* force loading from storage if needed */
    CPLErr eErr = OGRLayer::SetMetadata(papszMetadata, pszDomain);
    m_poDS->SetMetadataDirty();
    if( pszDomain == NULL || EQUAL(pszDomain, "") )
    {
        if( m_osIdentifierLCO.size() )
            OGRLayer::SetMetadataItem("IDENTIFIER", m_osIdentifierLCO);
        if( m_osDescriptionLCO.size() )
            OGRLayer::SetMetadataItem("DESCRIPTION", m_osDescriptionLCO);
    }
    return eErr;
}

/************************************************************************/
/*                          SetMetadataItem()                           */
/************************************************************************/

CPLErr OGRGeoPackageTableLayer::SetMetadataItem( const char * pszName,
                                                 const char * pszValue,
                                                 const char * pszDomain )
{
    GetMetadata(); /* force loading from storage if needed */
    if( m_osIdentifierLCO.size() && EQUAL(pszName, "IDENTIFIER") &&
        (pszDomain == NULL || EQUAL(pszDomain, "")) )
        return CE_None;
    if( m_osDescriptionLCO.size() && EQUAL(pszName, "DESCRIPTION") &&
        (pszDomain == NULL || EQUAL(pszDomain, "")) )
        return CE_None;
    m_poDS->SetMetadataDirty();
    return OGRLayer::SetMetadataItem(pszName, pszValue, pszDomain);
}
