LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gpkg - ogrgeopackagedatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 4014 4485 89.5 %
Date: 2025-05-07 12:05:40 Functions: 135 135 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GeoPackage Translator
       4             :  * Purpose:  Implements GDALGeoPackageDataset class
       5             :  * Author:   Paul Ramsey <pramsey@boundlessgeo.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2013, Paul Ramsey <pramsey@boundlessgeo.com>
       9             :  * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "ogr_geopackage.h"
      15             : #include "ogr_p.h"
      16             : #include "ogr_swq.h"
      17             : #include "gdalwarper.h"
      18             : #include "gdal_utils.h"
      19             : #include "ogrgeopackageutility.h"
      20             : #include "ogrsqliteutility.h"
      21             : #include "ogr_wkb.h"
      22             : #include "vrt/vrtdataset.h"
      23             : 
      24             : #include "tilematrixset.hpp"
      25             : 
      26             : #include <cstdlib>
      27             : 
      28             : #include <algorithm>
      29             : #include <limits>
      30             : #include <sstream>
      31             : 
      32             : #define COMPILATION_ALLOWED
      33             : #define DEFINE_OGRSQLiteSQLFunctionsSetCaseSensitiveLike
      34             : #include "ogrsqlitesqlfunctionscommon.cpp"
      35             : 
      36             : // Keep in sync prototype of those 2 functions between gdalopeninfo.cpp,
      37             : // ogrsqlitedatasource.cpp and ogrgeopackagedatasource.cpp
      38             : void GDALOpenInfoDeclareFileNotToOpen(const char *pszFilename,
      39             :                                       const GByte *pabyHeader,
      40             :                                       int nHeaderBytes);
      41             : void GDALOpenInfoUnDeclareFileNotToOpen(const char *pszFilename);
      42             : 
      43             : /************************************************************************/
      44             : /*                             Tiling schemes                           */
      45             : /************************************************************************/
      46             : 
      47             : typedef struct
      48             : {
      49             :     const char *pszName;
      50             :     int nEPSGCode;
      51             :     double dfMinX;
      52             :     double dfMaxY;
      53             :     int nTileXCountZoomLevel0;
      54             :     int nTileYCountZoomLevel0;
      55             :     int nTileWidth;
      56             :     int nTileHeight;
      57             :     double dfPixelXSizeZoomLevel0;
      58             :     double dfPixelYSizeZoomLevel0;
      59             : } TilingSchemeDefinition;
      60             : 
      61             : static const TilingSchemeDefinition asTilingSchemes[] = {
      62             :     /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
      63             :        Annex E.3 */
      64             :     {"GoogleCRS84Quad", 4326, -180.0, 180.0, 1, 1, 256, 256, 360.0 / 256,
      65             :      360.0 / 256},
      66             : 
      67             :     /* See global-mercator at
      68             :        http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
      69             :     {"PseudoTMS_GlobalMercator", 3857, -20037508.34, 20037508.34, 2, 2, 256,
      70             :      256, 78271.516, 78271.516},
      71             : };
      72             : 
      73             : // Setting it above 30 would lead to integer overflow ((1 << 31) > INT_MAX)
      74             : constexpr int MAX_ZOOM_LEVEL = 30;
      75             : 
      76             : /************************************************************************/
      77             : /*                     GetTilingScheme()                                */
      78             : /************************************************************************/
      79             : 
      80             : static std::unique_ptr<TilingSchemeDefinition>
      81         562 : GetTilingScheme(const char *pszName)
      82             : {
      83         562 :     if (EQUAL(pszName, "CUSTOM"))
      84         434 :         return nullptr;
      85             : 
      86         256 :     for (const auto &tilingScheme : asTilingSchemes)
      87             :     {
      88         195 :         if (EQUAL(pszName, tilingScheme.pszName))
      89             :         {
      90          67 :             return std::make_unique<TilingSchemeDefinition>(tilingScheme);
      91             :         }
      92             :     }
      93             : 
      94          61 :     if (EQUAL(pszName, "PseudoTMS_GlobalGeodetic"))
      95           6 :         pszName = "InspireCRS84Quad";
      96             : 
      97         122 :     auto poTM = gdal::TileMatrixSet::parse(pszName);
      98          61 :     if (poTM == nullptr)
      99           1 :         return nullptr;
     100          60 :     if (!poTM->haveAllLevelsSameTopLeft())
     101             :     {
     102           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     103             :                  "Unsupported tiling scheme: not all zoom levels have same top "
     104             :                  "left corner");
     105           0 :         return nullptr;
     106             :     }
     107          60 :     if (!poTM->haveAllLevelsSameTileSize())
     108             :     {
     109           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     110             :                  "Unsupported tiling scheme: not all zoom levels have same "
     111             :                  "tile size");
     112           0 :         return nullptr;
     113             :     }
     114          60 :     if (!poTM->hasOnlyPowerOfTwoVaryingScales())
     115             :     {
     116           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     117             :                  "Unsupported tiling scheme: resolution of consecutive zoom "
     118             :                  "levels is not always 2");
     119           1 :         return nullptr;
     120             :     }
     121          59 :     if (poTM->hasVariableMatrixWidth())
     122             :     {
     123           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     124             :                  "Unsupported tiling scheme: some levels have variable matrix "
     125             :                  "width");
     126           0 :         return nullptr;
     127             :     }
     128         118 :     auto poTilingScheme = std::make_unique<TilingSchemeDefinition>();
     129          59 :     poTilingScheme->pszName = pszName;
     130             : 
     131         118 :     OGRSpatialReference oSRS;
     132          59 :     if (oSRS.SetFromUserInput(poTM->crs().c_str()) != OGRERR_NONE)
     133             :     {
     134           0 :         return nullptr;
     135             :     }
     136          59 :     if (poTM->crs() == "http://www.opengis.net/def/crs/OGC/1.3/CRS84")
     137             :     {
     138           6 :         poTilingScheme->nEPSGCode = 4326;
     139             :     }
     140             :     else
     141             :     {
     142          53 :         const char *pszAuthName = oSRS.GetAuthorityName(nullptr);
     143          53 :         const char *pszAuthCode = oSRS.GetAuthorityCode(nullptr);
     144          53 :         if (pszAuthName == nullptr || !EQUAL(pszAuthName, "EPSG") ||
     145             :             pszAuthCode == nullptr)
     146             :         {
     147           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     148             :                      "Unsupported tiling scheme: only EPSG CRS supported");
     149           0 :             return nullptr;
     150             :         }
     151          53 :         poTilingScheme->nEPSGCode = atoi(pszAuthCode);
     152             :     }
     153          59 :     const auto &zoomLevel0 = poTM->tileMatrixList()[0];
     154          59 :     poTilingScheme->dfMinX = zoomLevel0.mTopLeftX;
     155          59 :     poTilingScheme->dfMaxY = zoomLevel0.mTopLeftY;
     156          59 :     poTilingScheme->nTileXCountZoomLevel0 = zoomLevel0.mMatrixWidth;
     157          59 :     poTilingScheme->nTileYCountZoomLevel0 = zoomLevel0.mMatrixHeight;
     158          59 :     poTilingScheme->nTileWidth = zoomLevel0.mTileWidth;
     159          59 :     poTilingScheme->nTileHeight = zoomLevel0.mTileHeight;
     160          59 :     poTilingScheme->dfPixelXSizeZoomLevel0 = zoomLevel0.mResX;
     161          59 :     poTilingScheme->dfPixelYSizeZoomLevel0 = zoomLevel0.mResY;
     162             : 
     163         118 :     const bool bInvertAxis = oSRS.EPSGTreatsAsLatLong() != FALSE ||
     164          59 :                              oSRS.EPSGTreatsAsNorthingEasting() != FALSE;
     165          59 :     if (bInvertAxis)
     166             :     {
     167           6 :         std::swap(poTilingScheme->dfMinX, poTilingScheme->dfMaxY);
     168           6 :         std::swap(poTilingScheme->dfPixelXSizeZoomLevel0,
     169           6 :                   poTilingScheme->dfPixelYSizeZoomLevel0);
     170             :     }
     171          59 :     return poTilingScheme;
     172             : }
     173             : 
     174             : static const char *pszCREATE_GPKG_GEOMETRY_COLUMNS =
     175             :     "CREATE TABLE gpkg_geometry_columns ("
     176             :     "table_name TEXT NOT NULL,"
     177             :     "column_name TEXT NOT NULL,"
     178             :     "geometry_type_name TEXT NOT NULL,"
     179             :     "srs_id INTEGER NOT NULL,"
     180             :     "z TINYINT NOT NULL,"
     181             :     "m TINYINT NOT NULL,"
     182             :     "CONSTRAINT pk_geom_cols PRIMARY KEY (table_name, column_name),"
     183             :     "CONSTRAINT uk_gc_table_name UNIQUE (table_name),"
     184             :     "CONSTRAINT fk_gc_tn FOREIGN KEY (table_name) REFERENCES "
     185             :     "gpkg_contents(table_name),"
     186             :     "CONSTRAINT fk_gc_srs FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys "
     187             :     "(srs_id)"
     188             :     ")";
     189             : 
     190         846 : OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
     191             : {
     192         846 :     CPLAssert(hDB != nullptr);
     193             : 
     194         846 :     const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
     195             :                                                 "PRAGMA user_version = %u",
     196             :                                                 m_nApplicationId,
     197        1692 :                                                 m_nUserVersion));
     198        1692 :     return SQLCommand(hDB, osPragma.c_str());
     199             : }
     200             : 
     201        2384 : bool GDALGeoPackageDataset::CloseDB()
     202             : {
     203        2384 :     OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
     204        2384 :     m_pSQLFunctionData = nullptr;
     205        2384 :     return OGRSQLiteBaseDataSource::CloseDB();
     206             : }
     207             : 
     208          11 : bool GDALGeoPackageDataset::ReOpenDB()
     209             : {
     210          11 :     CPLAssert(hDB != nullptr);
     211          11 :     CPLAssert(m_pszFilename != nullptr);
     212             : 
     213          11 :     FinishSpatialite();
     214             : 
     215          11 :     CloseDB();
     216             : 
     217             :     /* And re-open the file */
     218          11 :     return OpenOrCreateDB(SQLITE_OPEN_READWRITE);
     219             : }
     220             : 
     221         775 : static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
     222             :                                      int nEPSGCode)
     223             : {
     224         775 :     CPLPushErrorHandler(CPLQuietErrorHandler);
     225         775 :     const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
     226         775 :     CPLPopErrorHandler();
     227         775 :     CPLErrorReset();
     228         775 :     return eErr;
     229             : }
     230             : 
     231             : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
     232        1183 : GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
     233             :                                      bool bEmitErrorIfNotFound)
     234             : {
     235        1183 :     const auto oIter = m_oMapSrsIdToSrs.find(iSrsId);
     236        1183 :     if (oIter != m_oMapSrsIdToSrs.end())
     237             :     {
     238          82 :         if (oIter->second == nullptr)
     239          31 :             return nullptr;
     240          51 :         oIter->second->Reference();
     241             :         return std::unique_ptr<OGRSpatialReference,
     242          51 :                                OGRSpatialReferenceReleaser>(oIter->second);
     243             :     }
     244             : 
     245        1101 :     if (iSrsId == 0 || iSrsId == -1)
     246             :     {
     247         120 :         OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
     248         120 :         poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     249             : 
     250             :         // See corresponding tests in GDALGeoPackageDataset::GetSrsId
     251         120 :         if (iSrsId == 0)
     252             :         {
     253          29 :             poSpatialRef->SetGeogCS("Undefined geographic SRS", "unknown",
     254             :                                     "unknown", SRS_WGS84_SEMIMAJOR,
     255             :                                     SRS_WGS84_INVFLATTENING);
     256             :         }
     257          91 :         else if (iSrsId == -1)
     258             :         {
     259          91 :             poSpatialRef->SetLocalCS("Undefined Cartesian SRS");
     260          91 :             poSpatialRef->SetLinearUnits(SRS_UL_METER, 1.0);
     261             :         }
     262             : 
     263         120 :         m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
     264         120 :         poSpatialRef->Reference();
     265             :         return std::unique_ptr<OGRSpatialReference,
     266         120 :                                OGRSpatialReferenceReleaser>(poSpatialRef);
     267             :     }
     268             : 
     269        1962 :     CPLString oSQL;
     270         981 :     oSQL.Printf("SELECT srs_name, definition, organization, "
     271             :                 "organization_coordsys_id%s%s "
     272             :                 "FROM gpkg_spatial_ref_sys WHERE "
     273             :                 "srs_id = %d LIMIT 2",
     274         981 :                 m_bHasDefinition12_063 ? ", definition_12_063" : "",
     275         981 :                 m_bHasEpochColumn ? ", epoch" : "", iSrsId);
     276             : 
     277        1962 :     auto oResult = SQLQuery(hDB, oSQL.c_str());
     278             : 
     279         981 :     if (!oResult || oResult->RowCount() != 1)
     280             :     {
     281          12 :         if (bFallbackToEPSG)
     282             :         {
     283           7 :             CPLDebug("GPKG",
     284             :                      "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
     285             :                      iSrsId);
     286           7 :             OGRSpatialReference *poSRS = new OGRSpatialReference();
     287           7 :             if (poSRS->importFromEPSG(iSrsId) == OGRERR_NONE)
     288             :             {
     289           5 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     290             :                 return std::unique_ptr<OGRSpatialReference,
     291           5 :                                        OGRSpatialReferenceReleaser>(poSRS);
     292             :             }
     293           2 :             poSRS->Release();
     294             :         }
     295           5 :         else if (bEmitErrorIfNotFound)
     296             :         {
     297           2 :             CPLError(CE_Warning, CPLE_AppDefined,
     298             :                      "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
     299             :                      iSrsId);
     300           2 :             m_oMapSrsIdToSrs[iSrsId] = nullptr;
     301             :         }
     302           7 :         return nullptr;
     303             :     }
     304             : 
     305         969 :     const char *pszName = oResult->GetValue(0, 0);
     306         969 :     if (pszName && EQUAL(pszName, "Undefined SRS"))
     307             :     {
     308         404 :         m_oMapSrsIdToSrs[iSrsId] = nullptr;
     309         404 :         return nullptr;
     310             :     }
     311         565 :     const char *pszWkt = oResult->GetValue(1, 0);
     312         565 :     if (pszWkt == nullptr)
     313           0 :         return nullptr;
     314         565 :     const char *pszOrganization = oResult->GetValue(2, 0);
     315         565 :     const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
     316             :     const char *pszWkt2 =
     317         565 :         m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
     318         565 :     if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
     319          76 :         pszWkt = pszWkt2;
     320             :     const char *pszCoordinateEpoch =
     321         565 :         m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
     322             :     const double dfCoordinateEpoch =
     323         565 :         pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
     324             : 
     325         565 :     OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
     326         565 :     poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     327             :     // Try to import first from EPSG code, and then from WKT
     328         565 :     if (!(pszOrganization && pszOrganizationCoordsysID &&
     329         565 :           EQUAL(pszOrganization, "EPSG") &&
     330         545 :           (atoi(pszOrganizationCoordsysID) == iSrsId ||
     331           4 :            (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
     332         545 :           GDALGPKGImportFromEPSG(
     333        1130 :               poSpatialRef, atoi(pszOrganizationCoordsysID)) == OGRERR_NONE) &&
     334          20 :         poSpatialRef->importFromWkt(pszWkt) != OGRERR_NONE)
     335             :     {
     336           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     337             :                  "Unable to parse srs_id '%d' well-known text '%s'", iSrsId,
     338             :                  pszWkt);
     339           0 :         delete poSpatialRef;
     340           0 :         m_oMapSrsIdToSrs[iSrsId] = nullptr;
     341           0 :         return nullptr;
     342             :     }
     343             : 
     344         565 :     poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
     345         565 :     poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
     346         565 :     m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
     347         565 :     poSpatialRef->Reference();
     348             :     return std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>(
     349         565 :         poSpatialRef);
     350             : }
     351             : 
     352         259 : const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
     353             : {
     354         259 :     const char *pszName = oSRS.GetName();
     355         259 :     if (pszName)
     356         259 :         return pszName;
     357             : 
     358             :     // Something odd.  Return empty.
     359           0 :     return "Unnamed SRS";
     360             : }
     361             : 
     362             : /* Add the definition_12_063 column to an existing gpkg_spatial_ref_sys table */
     363           7 : bool GDALGeoPackageDataset::ConvertGpkgSpatialRefSysToExtensionWkt2(
     364             :     bool bForceEpoch)
     365             : {
     366           7 :     const bool bAddEpoch = (m_nUserVersion >= GPKG_1_4_VERSION || bForceEpoch);
     367             :     auto oResultTable = SQLQuery(
     368             :         hDB, "SELECT srs_name, srs_id, organization, organization_coordsys_id, "
     369          14 :              "definition, description FROM gpkg_spatial_ref_sys LIMIT 100000");
     370           7 :     if (!oResultTable)
     371           0 :         return false;
     372             : 
     373             :     // Temporary remove foreign key checks
     374             :     const GPKGTemporaryForeignKeyCheckDisabler
     375           7 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
     376             : 
     377           7 :     bool bRet = SoftStartTransaction() == OGRERR_NONE;
     378             : 
     379           7 :     if (bRet)
     380             :     {
     381             :         std::string osSQL("CREATE TABLE gpkg_spatial_ref_sys_temp ("
     382             :                           "srs_name TEXT NOT NULL,"
     383             :                           "srs_id INTEGER NOT NULL PRIMARY KEY,"
     384             :                           "organization TEXT NOT NULL,"
     385             :                           "organization_coordsys_id INTEGER NOT NULL,"
     386             :                           "definition TEXT NOT NULL,"
     387             :                           "description TEXT, "
     388           7 :                           "definition_12_063 TEXT NOT NULL");
     389           7 :         if (bAddEpoch)
     390           6 :             osSQL += ", epoch DOUBLE";
     391           7 :         osSQL += ")";
     392           7 :         bRet = SQLCommand(hDB, osSQL.c_str()) == OGRERR_NONE;
     393             :     }
     394             : 
     395           7 :     if (bRet)
     396             :     {
     397          32 :         for (int i = 0; bRet && i < oResultTable->RowCount(); i++)
     398             :         {
     399          25 :             const char *pszSrsName = oResultTable->GetValue(0, i);
     400          25 :             const char *pszSrsId = oResultTable->GetValue(1, i);
     401          25 :             const char *pszOrganization = oResultTable->GetValue(2, i);
     402             :             const char *pszOrganizationCoordsysID =
     403          25 :                 oResultTable->GetValue(3, i);
     404          25 :             const char *pszDefinition = oResultTable->GetValue(4, i);
     405             :             if (pszSrsName == nullptr || pszSrsId == nullptr ||
     406             :                 pszOrganization == nullptr ||
     407             :                 pszOrganizationCoordsysID == nullptr)
     408             :             {
     409             :                 // should not happen as there are NOT NULL constraints
     410             :                 // But a database could lack such NOT NULL constraints or have
     411             :                 // large values that would cause a memory allocation failure.
     412             :             }
     413          25 :             const char *pszDescription = oResultTable->GetValue(5, i);
     414             :             char *pszSQL;
     415             : 
     416          50 :             OGRSpatialReference oSRS;
     417          25 :             if (pszOrganization && pszOrganizationCoordsysID &&
     418          25 :                 EQUAL(pszOrganization, "EPSG"))
     419             :             {
     420           9 :                 oSRS.importFromEPSG(atoi(pszOrganizationCoordsysID));
     421             :             }
     422          34 :             if (!oSRS.IsEmpty() && pszDefinition &&
     423           9 :                 !EQUAL(pszDefinition, "undefined"))
     424             :             {
     425           9 :                 oSRS.SetFromUserInput(pszDefinition);
     426             :             }
     427          25 :             char *pszWKT2 = nullptr;
     428          25 :             if (!oSRS.IsEmpty())
     429             :             {
     430           9 :                 const char *const apszOptionsWkt2[] = {"FORMAT=WKT2_2015",
     431             :                                                        nullptr};
     432           9 :                 oSRS.exportToWkt(&pszWKT2, apszOptionsWkt2);
     433           9 :                 if (pszWKT2 && pszWKT2[0] == '\0')
     434             :                 {
     435           0 :                     CPLFree(pszWKT2);
     436           0 :                     pszWKT2 = nullptr;
     437             :                 }
     438             :             }
     439          25 :             if (pszWKT2 == nullptr)
     440             :             {
     441          16 :                 pszWKT2 = CPLStrdup("undefined");
     442             :             }
     443             : 
     444          25 :             if (pszDescription)
     445             :             {
     446          22 :                 pszSQL = sqlite3_mprintf(
     447             :                     "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
     448             :                     "organization, organization_coordsys_id, definition, "
     449             :                     "description, definition_12_063) VALUES ('%q', '%q', '%q', "
     450             :                     "'%q', '%q', '%q', '%q')",
     451             :                     pszSrsName, pszSrsId, pszOrganization,
     452             :                     pszOrganizationCoordsysID, pszDefinition, pszDescription,
     453             :                     pszWKT2);
     454             :             }
     455             :             else
     456             :             {
     457           3 :                 pszSQL = sqlite3_mprintf(
     458             :                     "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
     459             :                     "organization, organization_coordsys_id, definition, "
     460             :                     "description, definition_12_063) VALUES ('%q', '%q', '%q', "
     461             :                     "'%q', '%q', NULL, '%q')",
     462             :                     pszSrsName, pszSrsId, pszOrganization,
     463             :                     pszOrganizationCoordsysID, pszDefinition, pszWKT2);
     464             :             }
     465             : 
     466          25 :             CPLFree(pszWKT2);
     467          25 :             bRet &= SQLCommand(hDB, pszSQL) == OGRERR_NONE;
     468          25 :             sqlite3_free(pszSQL);
     469             :         }
     470             :     }
     471             : 
     472           7 :     if (bRet)
     473             :     {
     474           7 :         bRet =
     475           7 :             SQLCommand(hDB, "DROP TABLE gpkg_spatial_ref_sys") == OGRERR_NONE;
     476             :     }
     477           7 :     if (bRet)
     478             :     {
     479           7 :         bRet = SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys_temp RENAME "
     480             :                                "TO gpkg_spatial_ref_sys") == OGRERR_NONE;
     481             :     }
     482           7 :     if (bRet)
     483             :     {
     484          14 :         bRet = OGRERR_NONE == CreateExtensionsTableIfNecessary() &&
     485           7 :                OGRERR_NONE == SQLCommand(hDB,
     486             :                                          "INSERT INTO gpkg_extensions "
     487             :                                          "(table_name, column_name, "
     488             :                                          "extension_name, definition, scope) "
     489             :                                          "VALUES "
     490             :                                          "('gpkg_spatial_ref_sys', "
     491             :                                          "'definition_12_063', 'gpkg_crs_wkt', "
     492             :                                          "'http://www.geopackage.org/spec120/"
     493             :                                          "#extension_crs_wkt', 'read-write')");
     494             :     }
     495           7 :     if (bRet && bAddEpoch)
     496             :     {
     497           6 :         bRet =
     498             :             OGRERR_NONE ==
     499           6 :                 SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
     500             :                                 "'gpkg_crs_wkt_1_1' "
     501          12 :                                 "WHERE extension_name = 'gpkg_crs_wkt'") &&
     502             :             OGRERR_NONE ==
     503           6 :                 SQLCommand(
     504             :                     hDB,
     505             :                     "INSERT INTO gpkg_extensions "
     506             :                     "(table_name, column_name, extension_name, definition, "
     507             :                     "scope) "
     508             :                     "VALUES "
     509             :                     "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
     510             :                     "'http://www.geopackage.org/spec/#extension_crs_wkt', "
     511             :                     "'read-write')");
     512             :     }
     513           7 :     if (bRet)
     514             :     {
     515           7 :         SoftCommitTransaction();
     516           7 :         m_bHasDefinition12_063 = true;
     517           7 :         if (bAddEpoch)
     518           6 :             m_bHasEpochColumn = true;
     519             :     }
     520             :     else
     521             :     {
     522           0 :         SoftRollbackTransaction();
     523             :     }
     524             : 
     525           7 :     return bRet;
     526             : }
     527             : 
     528         830 : int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
     529             : {
     530         830 :     const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
     531        1210 :     if (!poSRSIn || poSRSIn->IsEmpty() ||
     532         380 :         (pszName && EQUAL(pszName, "Undefined SRS")))
     533             :     {
     534         452 :         OGRErr err = OGRERR_NONE;
     535         452 :         const int nSRSId = SQLGetInteger(
     536             :             hDB,
     537             :             "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE srs_name = "
     538             :             "'Undefined SRS' AND organization = 'GDAL'",
     539             :             &err);
     540         452 :         if (err == OGRERR_NONE)
     541          54 :             return nSRSId;
     542             : 
     543             :         // The below WKT definitions are somehow questionable (using a unknown
     544             :         // unit). For GDAL >= 3.9, they won't be used. They will only be used
     545             :         // for earlier versions.
     546             :         const char *pszSQL;
     547             : #define UNDEFINED_CRS_SRS_ID 99999
     548             :         static_assert(UNDEFINED_CRS_SRS_ID == FIRST_CUSTOM_SRSID - 1);
     549             : #define STRINGIFY(x) #x
     550             : #define XSTRINGIFY(x) STRINGIFY(x)
     551         398 :         if (m_bHasDefinition12_063)
     552             :         {
     553             :             /* clang-format off */
     554           1 :             pszSQL =
     555             :                 "INSERT INTO gpkg_spatial_ref_sys "
     556             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     557             :                 "definition, definition_12_063, description) VALUES "
     558             :                 "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
     559             :                 XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
     560             :                 "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
     561             :                 "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
     562             :                 "AXIS[\"Northing\",NORTH]]',"
     563             :                 "'ENGCRS[\"Undefined SRS\",EDATUM[\"unknown\"],CS[Cartesian,2],"
     564             :                 "AXIS[\"easting\",east,ORDER[1],LENGTHUNIT[\"unknown\",0]],"
     565             :                 "AXIS[\"northing\",north,ORDER[2],LENGTHUNIT[\"unknown\",0]]]',"
     566             :                 "'Custom undefined coordinate reference system')";
     567             :             /* clang-format on */
     568             :         }
     569             :         else
     570             :         {
     571             :             /* clang-format off */
     572         397 :             pszSQL =
     573             :                 "INSERT INTO gpkg_spatial_ref_sys "
     574             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     575             :                 "definition, description) VALUES "
     576             :                 "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
     577             :                 XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
     578             :                 "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
     579             :                 "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
     580             :                 "AXIS[\"Northing\",NORTH]]',"
     581             :                 "'Custom undefined coordinate reference system')";
     582             :             /* clang-format on */
     583             :         }
     584         398 :         if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
     585         398 :             return UNDEFINED_CRS_SRS_ID;
     586             : #undef UNDEFINED_CRS_SRS_ID
     587             : #undef XSTRINGIFY
     588             : #undef STRINGIFY
     589           0 :         return -1;
     590             :     }
     591             : 
     592         756 :     std::unique_ptr<OGRSpatialReference> poSRS(poSRSIn->Clone());
     593             : 
     594         378 :     if (poSRS->IsGeographic() || poSRS->IsLocal())
     595             :     {
     596             :         // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
     597         138 :         if (pszName != nullptr && strlen(pszName) > 0)
     598             :         {
     599         138 :             if (EQUAL(pszName, "Undefined geographic SRS"))
     600           2 :                 return 0;
     601             : 
     602         136 :             if (EQUAL(pszName, "Undefined Cartesian SRS"))
     603           1 :                 return -1;
     604             :         }
     605             :     }
     606             : 
     607         375 :     const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     608             : 
     609         375 :     if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0)
     610             :     {
     611             :         // Try to force identify an EPSG code.
     612          26 :         poSRS->AutoIdentifyEPSG();
     613             : 
     614          26 :         pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     615          26 :         if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG"))
     616             :         {
     617           0 :             const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
     618           0 :             if (pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0)
     619             :             {
     620             :                 /* Import 'clean' SRS */
     621           0 :                 poSRS->importFromEPSG(atoi(pszAuthorityCode));
     622             : 
     623           0 :                 pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     624             :             }
     625             :         }
     626             : 
     627          26 :         poSRS->SetCoordinateEpoch(poSRSIn->GetCoordinateEpoch());
     628             :     }
     629             : 
     630             :     // Check whether the EPSG authority code is already mapped to a
     631             :     // SRS ID.
     632         375 :     char *pszSQL = nullptr;
     633         375 :     int nSRSId = DEFAULT_SRID;
     634         375 :     int nAuthorityCode = 0;
     635         375 :     OGRErr err = OGRERR_NONE;
     636         375 :     bool bCanUseAuthorityCode = false;
     637         375 :     const char *const apszIsSameOptions[] = {
     638             :         "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
     639             :         "IGNORE_COORDINATE_EPOCH=YES", nullptr};
     640         375 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
     641             :     {
     642         349 :         const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
     643         349 :         if (pszAuthorityCode)
     644             :         {
     645         349 :             if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
     646             :             {
     647         349 :                 nAuthorityCode = atoi(pszAuthorityCode);
     648             :             }
     649             :             else
     650             :             {
     651           0 :                 CPLDebug("GPKG",
     652             :                          "SRS has %s:%s identification, but the code not "
     653             :                          "being an integer value cannot be stored as such "
     654             :                          "in the database.",
     655             :                          pszAuthorityName, pszAuthorityCode);
     656           0 :                 pszAuthorityName = nullptr;
     657             :             }
     658             :         }
     659             :     }
     660             : 
     661         724 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
     662         349 :         poSRSIn->GetCoordinateEpoch() == 0)
     663             :     {
     664             :         pszSQL =
     665         344 :             sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     666             :                             "upper(organization) = upper('%q') AND "
     667             :                             "organization_coordsys_id = %d",
     668             :                             pszAuthorityName, nAuthorityCode);
     669             : 
     670         344 :         nSRSId = SQLGetInteger(hDB, pszSQL, &err);
     671         344 :         sqlite3_free(pszSQL);
     672             : 
     673             :         // Got a match? Return it!
     674         344 :         if (OGRERR_NONE == err)
     675             :         {
     676         112 :             auto poRefSRS = GetSpatialRef(nSRSId);
     677             :             bool bOK =
     678         112 :                 (poRefSRS == nullptr ||
     679         113 :                  poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) ||
     680           1 :                  !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
     681         112 :             if (bOK)
     682             :             {
     683         111 :                 return nSRSId;
     684             :             }
     685             :             else
     686             :             {
     687           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
     688             :                          "Passed SRS uses %s:%d identification, but its "
     689             :                          "definition is not compatible with the "
     690             :                          "definition of that object already in the database. "
     691             :                          "Registering it as a new entry into the database.",
     692             :                          pszAuthorityName, nAuthorityCode);
     693           1 :                 pszAuthorityName = nullptr;
     694           1 :                 nAuthorityCode = 0;
     695             :             }
     696             :         }
     697             :     }
     698             : 
     699             :     // Translate SRS to WKT.
     700         264 :     CPLCharUniquePtr pszWKT1;
     701         264 :     CPLCharUniquePtr pszWKT2_2015;
     702         264 :     CPLCharUniquePtr pszWKT2_2019;
     703         264 :     const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
     704         264 :     const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
     705         264 :     const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
     706             : 
     707         528 :     std::string osEpochTest;
     708         264 :     if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
     709             :     {
     710             :         osEpochTest =
     711           3 :             CPLSPrintf(" AND epoch = %.17g", poSRSIn->GetCoordinateEpoch());
     712             :     }
     713             : 
     714         264 :     if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3))
     715             :     {
     716         255 :         char *pszTmp = nullptr;
     717         255 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
     718         255 :         pszWKT1.reset(pszTmp);
     719         255 :         if (pszWKT1 && pszWKT1.get()[0] == '\0')
     720             :         {
     721           0 :             pszWKT1.reset();
     722             :         }
     723             :     }
     724             :     {
     725         264 :         char *pszTmp = nullptr;
     726         264 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
     727         264 :         pszWKT2_2015.reset(pszTmp);
     728         264 :         if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
     729             :         {
     730           0 :             pszWKT2_2015.reset();
     731             :         }
     732             :     }
     733             :     {
     734         264 :         char *pszTmp = nullptr;
     735         264 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
     736         264 :         pszWKT2_2019.reset(pszTmp);
     737         264 :         if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
     738             :         {
     739           0 :             pszWKT2_2019.reset();
     740             :         }
     741             :     }
     742             : 
     743         264 :     if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
     744             :     {
     745           0 :         return DEFAULT_SRID;
     746             :     }
     747             : 
     748         264 :     if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
     749             :     {
     750             :         // Search if there is already an existing entry with this WKT
     751         261 :         if (m_bHasDefinition12_063 && (pszWKT2_2015 || pszWKT2_2019))
     752             :         {
     753          42 :             if (pszWKT1)
     754             :             {
     755         144 :                 pszSQL = sqlite3_mprintf(
     756             :                     "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     757             :                     "(definition = '%q' OR definition_12_063 IN ('%q','%q'))%s",
     758             :                     pszWKT1.get(),
     759          72 :                     pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
     760          72 :                     pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
     761             :                     osEpochTest.c_str());
     762             :             }
     763             :             else
     764             :             {
     765          24 :                 pszSQL = sqlite3_mprintf(
     766             :                     "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     767             :                     "definition_12_063 IN ('%q', '%q')%s",
     768          12 :                     pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
     769          12 :                     pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
     770             :                     osEpochTest.c_str());
     771             :             }
     772             :         }
     773         219 :         else if (pszWKT1)
     774             :         {
     775             :             pszSQL =
     776         216 :                 sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     777             :                                 "definition = '%q'%s",
     778             :                                 pszWKT1.get(), osEpochTest.c_str());
     779             :         }
     780             :         else
     781             :         {
     782           3 :             pszSQL = nullptr;
     783             :         }
     784         261 :         if (pszSQL)
     785             :         {
     786         258 :             nSRSId = SQLGetInteger(hDB, pszSQL, &err);
     787         258 :             sqlite3_free(pszSQL);
     788         258 :             if (OGRERR_NONE == err)
     789             :             {
     790           5 :                 return nSRSId;
     791             :             }
     792             :         }
     793             :     }
     794             : 
     795         494 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
     796         235 :         poSRSIn->GetCoordinateEpoch() == 0)
     797             :     {
     798         231 :         bool bTryToReuseSRSId = true;
     799         231 :         if (EQUAL(pszAuthorityName, "EPSG"))
     800             :         {
     801         460 :             OGRSpatialReference oSRS_EPSG;
     802         230 :             if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
     803             :                 OGRERR_NONE)
     804             :             {
     805         231 :                 if (!poSRS->IsSame(&oSRS_EPSG, apszIsSameOptions) &&
     806           1 :                     CPLTestBool(
     807             :                         CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")))
     808             :                 {
     809           1 :                     bTryToReuseSRSId = false;
     810           1 :                     CPLError(
     811             :                         CE_Warning, CPLE_AppDefined,
     812             :                         "Passed SRS uses %s:%d identification, but its "
     813             :                         "definition is not compatible with the "
     814             :                         "official definition of the object. "
     815             :                         "Registering it as a non-%s entry into the database.",
     816             :                         pszAuthorityName, nAuthorityCode, pszAuthorityName);
     817           1 :                     pszAuthorityName = nullptr;
     818           1 :                     nAuthorityCode = 0;
     819             :                 }
     820             :             }
     821             :         }
     822         231 :         if (bTryToReuseSRSId)
     823             :         {
     824             :             // No match, but maybe we can use the nAuthorityCode as the nSRSId?
     825         230 :             pszSQL = sqlite3_mprintf(
     826             :                 "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
     827             :                 "srs_id = %d",
     828             :                 nAuthorityCode);
     829             : 
     830             :             // Yep, we can!
     831         230 :             if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
     832         229 :                 bCanUseAuthorityCode = true;
     833         230 :             sqlite3_free(pszSQL);
     834             :         }
     835             :     }
     836             : 
     837         259 :     bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
     838         259 :     bool bForceEpoch = false;
     839         262 :     if (!m_bHasDefinition12_063 && pszWKT1 == nullptr &&
     840           3 :         (pszWKT2_2015 != nullptr || pszWKT2_2019 != nullptr))
     841             :     {
     842           3 :         bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
     843             :     }
     844             : 
     845             :     // Add epoch column if needed
     846         259 :     if (poSRSIn->GetCoordinateEpoch() > 0 && !m_bHasEpochColumn)
     847             :     {
     848           3 :         if (m_bHasDefinition12_063)
     849             :         {
     850           0 :             if (SoftStartTransaction() != OGRERR_NONE)
     851           0 :                 return DEFAULT_SRID;
     852           0 :             if (SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys "
     853           0 :                                 "ADD COLUMN epoch DOUBLE") != OGRERR_NONE ||
     854           0 :                 SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
     855             :                                 "'gpkg_crs_wkt_1_1' "
     856             :                                 "WHERE extension_name = 'gpkg_crs_wkt'") !=
     857           0 :                     OGRERR_NONE ||
     858           0 :                 SQLCommand(
     859             :                     hDB,
     860             :                     "INSERT INTO gpkg_extensions "
     861             :                     "(table_name, column_name, extension_name, definition, "
     862             :                     "scope) "
     863             :                     "VALUES "
     864             :                     "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
     865             :                     "'http://www.geopackage.org/spec/#extension_crs_wkt', "
     866             :                     "'read-write')") != OGRERR_NONE)
     867             :             {
     868           0 :                 SoftRollbackTransaction();
     869           0 :                 return DEFAULT_SRID;
     870             :             }
     871             : 
     872           0 :             if (SoftCommitTransaction() != OGRERR_NONE)
     873           0 :                 return DEFAULT_SRID;
     874             : 
     875           0 :             m_bHasEpochColumn = true;
     876             :         }
     877             :         else
     878             :         {
     879           3 :             bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
     880           3 :             bForceEpoch = true;
     881             :         }
     882             :     }
     883             : 
     884         265 :     if (bConvertGpkgSpatialRefSysToExtensionWkt2 &&
     885           6 :         !ConvertGpkgSpatialRefSysToExtensionWkt2(bForceEpoch))
     886             :     {
     887           0 :         return DEFAULT_SRID;
     888             :     }
     889             : 
     890             :     // Reuse the authority code number as SRS_ID if we can
     891         259 :     if (bCanUseAuthorityCode)
     892             :     {
     893         229 :         nSRSId = nAuthorityCode;
     894             :     }
     895             :     // Otherwise, generate a new SRS_ID number (max + 1)
     896             :     else
     897             :     {
     898             :         // Get the current maximum srid in the srs table.
     899          30 :         const int nMaxSRSId = SQLGetInteger(
     900             :             hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
     901          30 :         nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
     902             :     }
     903             : 
     904         518 :     std::string osEpochColumn;
     905         259 :     std::string osEpochVal;
     906         259 :     if (poSRSIn->GetCoordinateEpoch() > 0)
     907             :     {
     908           5 :         osEpochColumn = ", epoch";
     909           5 :         osEpochVal = CPLSPrintf(", %.17g", poSRSIn->GetCoordinateEpoch());
     910             :     }
     911             : 
     912             :     // Add new SRS row to gpkg_spatial_ref_sys.
     913         259 :     if (m_bHasDefinition12_063)
     914             :     {
     915             :         // Force WKT2_2019 when we have a dynamic CRS and coordinate epoch
     916          45 :         const char *pszWKT2 = poSRSIn->IsDynamic() &&
     917          10 :                                       poSRSIn->GetCoordinateEpoch() > 0 &&
     918           1 :                                       pszWKT2_2019
     919           1 :                                   ? pszWKT2_2019.get()
     920          44 :                               : pszWKT2_2015 ? pszWKT2_2015.get()
     921          97 :                                              : pszWKT2_2019.get();
     922             : 
     923          45 :         if (pszAuthorityName != nullptr && nAuthorityCode > 0)
     924             :         {
     925          99 :             pszSQL = sqlite3_mprintf(
     926             :                 "INSERT INTO gpkg_spatial_ref_sys "
     927             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     928             :                 "definition, definition_12_063%s) VALUES "
     929             :                 "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
     930          33 :                 osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId,
     931             :                 pszAuthorityName, nAuthorityCode,
     932          62 :                 pszWKT1 ? pszWKT1.get() : "undefined",
     933             :                 pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
     934             :         }
     935             :         else
     936             :         {
     937          36 :             pszSQL = sqlite3_mprintf(
     938             :                 "INSERT INTO gpkg_spatial_ref_sys "
     939             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     940             :                 "definition, definition_12_063%s) VALUES "
     941             :                 "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
     942          12 :                 osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId, "NONE",
     943          21 :                 nSRSId, pszWKT1 ? pszWKT1.get() : "undefined",
     944             :                 pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
     945             :         }
     946             :     }
     947             :     else
     948             :     {
     949         214 :         if (pszAuthorityName != nullptr && nAuthorityCode > 0)
     950             :         {
     951         402 :             pszSQL = sqlite3_mprintf(
     952             :                 "INSERT INTO gpkg_spatial_ref_sys "
     953             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     954             :                 "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
     955         201 :                 GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
     956         402 :                 pszWKT1 ? pszWKT1.get() : "undefined");
     957             :         }
     958             :         else
     959             :         {
     960          26 :             pszSQL = sqlite3_mprintf(
     961             :                 "INSERT INTO gpkg_spatial_ref_sys "
     962             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     963             :                 "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
     964          13 :                 GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
     965          26 :                 pszWKT1 ? pszWKT1.get() : "undefined");
     966             :         }
     967             :     }
     968             : 
     969             :     // Add new row to gpkg_spatial_ref_sys.
     970         259 :     CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
     971             : 
     972             :     // Free everything that was allocated.
     973         259 :     sqlite3_free(pszSQL);
     974             : 
     975         259 :     return nSRSId;
     976             : }
     977             : 
     978             : /************************************************************************/
     979             : /*                       ~GDALGeoPackageDataset()                       */
     980             : /************************************************************************/
     981             : 
     982        4746 : GDALGeoPackageDataset::~GDALGeoPackageDataset()
     983             : {
     984        2373 :     GDALGeoPackageDataset::Close();
     985        4746 : }
     986             : 
     987             : /************************************************************************/
     988             : /*                              Close()                                 */
     989             : /************************************************************************/
     990             : 
     991        3993 : CPLErr GDALGeoPackageDataset::Close()
     992             : {
     993        3993 :     CPLErr eErr = CE_None;
     994        3993 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
     995             :     {
     996        1380 :         if (eAccess == GA_Update && m_poParentDS == nullptr &&
     997        3753 :             !m_osRasterTable.empty() && !m_bGeoTransformValid)
     998             :         {
     999           3 :             CPLError(CE_Failure, CPLE_AppDefined,
    1000             :                      "Raster table %s not correctly initialized due to missing "
    1001             :                      "call to SetGeoTransform()",
    1002             :                      m_osRasterTable.c_str());
    1003             :         }
    1004             : 
    1005        2373 :         if (GDALGeoPackageDataset::FlushCache(true) != CE_None)
    1006           7 :             eErr = CE_Failure;
    1007             : 
    1008             :         // Destroy bands now since we don't want
    1009             :         // GDALGPKGMBTilesLikeRasterBand::FlushCache() to run after dataset
    1010             :         // destruction
    1011        4190 :         for (int i = 0; i < nBands; i++)
    1012        1817 :             delete papoBands[i];
    1013        2373 :         nBands = 0;
    1014        2373 :         CPLFree(papoBands);
    1015        2373 :         papoBands = nullptr;
    1016             : 
    1017             :         // Destroy overviews before cleaning m_hTempDB as they could still
    1018             :         // need it
    1019        2373 :         m_apoOverviewDS.clear();
    1020             : 
    1021        2373 :         if (m_poParentDS)
    1022             :         {
    1023         325 :             hDB = nullptr;
    1024             :         }
    1025             : 
    1026        2373 :         m_apoLayers.clear();
    1027             : 
    1028             :         std::map<int, OGRSpatialReference *>::iterator oIter =
    1029        2373 :             m_oMapSrsIdToSrs.begin();
    1030        3464 :         for (; oIter != m_oMapSrsIdToSrs.end(); ++oIter)
    1031             :         {
    1032        1091 :             OGRSpatialReference *poSRS = oIter->second;
    1033        1091 :             if (poSRS)
    1034         685 :                 poSRS->Release();
    1035             :         }
    1036             : 
    1037        2373 :         if (!CloseDB())
    1038           0 :             eErr = CE_Failure;
    1039             : 
    1040        2373 :         if (OGRSQLiteBaseDataSource::Close() != CE_None)
    1041           0 :             eErr = CE_Failure;
    1042             :     }
    1043        3993 :     return eErr;
    1044             : }
    1045             : 
    1046             : /************************************************************************/
    1047             : /*                         ICanIWriteBlock()                            */
    1048             : /************************************************************************/
    1049             : 
    1050        5694 : bool GDALGeoPackageDataset::ICanIWriteBlock()
    1051             : {
    1052        5694 :     if (!GetUpdate())
    1053             :     {
    1054           0 :         CPLError(
    1055             :             CE_Failure, CPLE_NotSupported,
    1056             :             "IWriteBlock() not supported on dataset opened in read-only mode");
    1057           0 :         return false;
    1058             :     }
    1059             : 
    1060        5694 :     if (m_pabyCachedTiles == nullptr)
    1061             :     {
    1062           0 :         return false;
    1063             :     }
    1064             : 
    1065        5694 :     if (!m_bGeoTransformValid || m_nSRID == UNKNOWN_SRID)
    1066             :     {
    1067           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1068             :                  "IWriteBlock() not supported if georeferencing not set");
    1069           0 :         return false;
    1070             :     }
    1071        5694 :     return true;
    1072             : }
    1073             : 
    1074             : /************************************************************************/
    1075             : /*                            IRasterIO()                               */
    1076             : /************************************************************************/
    1077             : 
    1078         130 : CPLErr GDALGeoPackageDataset::IRasterIO(
    1079             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
    1080             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
    1081             :     int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
    1082             :     GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
    1083             : 
    1084             : {
    1085         130 :     CPLErr eErr = OGRSQLiteBaseDataSource::IRasterIO(
    1086             :         eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    1087             :         eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
    1088             :         psExtraArg);
    1089             : 
    1090             :     // If writing all bands, in non-shifted mode, flush all entirely written
    1091             :     // tiles This can avoid "stressing" the block cache with too many dirty
    1092             :     // blocks. Note: this logic would be useless with a per-dataset block cache.
    1093         130 :     if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
    1094         121 :         nYSize == nBufYSize && nBandCount == nBands &&
    1095         118 :         m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
    1096             :     {
    1097             :         auto poBand =
    1098         114 :             cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
    1099             :         int nBlockXSize, nBlockYSize;
    1100         114 :         poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
    1101         114 :         const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
    1102         114 :         const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
    1103         114 :         const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
    1104         114 :         const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
    1105         268 :         for (int nBlockY = nBlockXStart; nBlockY < nBlockYEnd; nBlockY++)
    1106             :         {
    1107        4371 :             for (int nBlockX = nBlockYStart; nBlockX < nBlockXEnd; nBlockX++)
    1108             :             {
    1109             :                 GDALRasterBlock *poBlock =
    1110        4217 :                     poBand->AccessibleTryGetLockedBlockRef(nBlockX, nBlockY);
    1111        4217 :                 if (poBlock)
    1112             :                 {
    1113             :                     // GetDirty() should be true in most situation (otherwise
    1114             :                     // it means the block cache is under extreme pressure!)
    1115        4215 :                     if (poBlock->GetDirty())
    1116             :                     {
    1117             :                         // IWriteBlock() on one band will check the dirty state
    1118             :                         // of the corresponding blocks in other bands, to decide
    1119             :                         // if it can call WriteTile(), so we have only to do
    1120             :                         // that on one of the bands
    1121        4215 :                         if (poBlock->Write() != CE_None)
    1122         250 :                             eErr = CE_Failure;
    1123             :                     }
    1124        4215 :                     poBlock->DropLock();
    1125             :                 }
    1126             :             }
    1127             :         }
    1128             :     }
    1129             : 
    1130         130 :     return eErr;
    1131             : }
    1132             : 
    1133             : /************************************************************************/
    1134             : /*                          GetOGRTableLimit()                          */
    1135             : /************************************************************************/
    1136             : 
    1137        3853 : static int GetOGRTableLimit()
    1138             : {
    1139        3853 :     return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
    1140             : }
    1141             : 
    1142             : /************************************************************************/
    1143             : /*                      GetNameTypeMapFromSQliteMaster()                */
    1144             : /************************************************************************/
    1145             : 
    1146             : const std::map<CPLString, CPLString> &
    1147        1193 : GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
    1148             : {
    1149        1193 :     if (!m_oMapNameToType.empty())
    1150         327 :         return m_oMapNameToType;
    1151             : 
    1152             :     CPLString osSQL(
    1153             :         "SELECT name, type FROM sqlite_master WHERE "
    1154             :         "type IN ('view', 'table') OR "
    1155        1732 :         "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
    1156         866 :     const int nTableLimit = GetOGRTableLimit();
    1157         866 :     if (nTableLimit > 0)
    1158             :     {
    1159         866 :         osSQL += " LIMIT ";
    1160         866 :         osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
    1161             :     }
    1162             : 
    1163         866 :     auto oResult = SQLQuery(hDB, osSQL);
    1164         866 :     if (oResult)
    1165             :     {
    1166       14482 :         for (int i = 0; i < oResult->RowCount(); i++)
    1167             :         {
    1168       13616 :             const char *pszName = oResult->GetValue(0, i);
    1169       13616 :             const char *pszType = oResult->GetValue(1, i);
    1170       13616 :             m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
    1171             :         }
    1172             :     }
    1173             : 
    1174         866 :     return m_oMapNameToType;
    1175             : }
    1176             : 
    1177             : /************************************************************************/
    1178             : /*                    RemoveTableFromSQLiteMasterCache()                */
    1179             : /************************************************************************/
    1180             : 
    1181          54 : void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
    1182             :     const char *pszTableName)
    1183             : {
    1184          54 :     m_oMapNameToType.erase(CPLString(pszTableName).toupper());
    1185          54 : }
    1186             : 
    1187             : /************************************************************************/
    1188             : /*                  GetUnknownExtensionsTableSpecific()                 */
    1189             : /************************************************************************/
    1190             : 
    1191             : const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
    1192         825 : GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
    1193             : {
    1194         825 :     if (m_bMapTableToExtensionsBuilt)
    1195          84 :         return m_oMapTableToExtensions;
    1196         741 :     m_bMapTableToExtensionsBuilt = true;
    1197             : 
    1198         741 :     if (!HasExtensionsTable())
    1199          38 :         return m_oMapTableToExtensions;
    1200             : 
    1201             :     CPLString osSQL(
    1202             :         "SELECT table_name, extension_name, definition, scope "
    1203             :         "FROM gpkg_extensions WHERE "
    1204             :         "table_name IS NOT NULL "
    1205             :         "AND extension_name IS NOT NULL "
    1206             :         "AND definition IS NOT NULL "
    1207             :         "AND scope IS NOT NULL "
    1208             :         "AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
    1209             :         "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', "
    1210             :         "'gpkg_geom_MULTICURVE', "
    1211             :         "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
    1212             :         "'gpkg_geom_POLYHEDRALSURFACE', 'gpkg_geom_TIN', 'gpkg_geom_TRIANGLE', "
    1213             :         "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', "
    1214             :         "'gpkg_srs_id_trigger', "
    1215             :         "'gpkg_crs_wkt', 'gpkg_crs_wkt_1_1', 'gpkg_schema', "
    1216             :         "'gpkg_related_tables', 'related_tables'"
    1217             : #ifdef HAVE_SPATIALITE
    1218             :         ", 'gdal_spatialite_computed_geom_column'"
    1219             : #endif
    1220        1406 :         ")");
    1221         703 :     const int nTableLimit = GetOGRTableLimit();
    1222         703 :     if (nTableLimit > 0)
    1223             :     {
    1224         703 :         osSQL += " LIMIT ";
    1225         703 :         osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
    1226             :     }
    1227             : 
    1228         703 :     auto oResult = SQLQuery(hDB, osSQL);
    1229         703 :     if (oResult)
    1230             :     {
    1231        1352 :         for (int i = 0; i < oResult->RowCount(); i++)
    1232             :         {
    1233         649 :             const char *pszTableName = oResult->GetValue(0, i);
    1234         649 :             const char *pszExtensionName = oResult->GetValue(1, i);
    1235         649 :             const char *pszDefinition = oResult->GetValue(2, i);
    1236         649 :             const char *pszScope = oResult->GetValue(3, i);
    1237         649 :             if (pszTableName && pszExtensionName && pszDefinition && pszScope)
    1238             :             {
    1239         649 :                 GPKGExtensionDesc oDesc;
    1240         649 :                 oDesc.osExtensionName = pszExtensionName;
    1241         649 :                 oDesc.osDefinition = pszDefinition;
    1242         649 :                 oDesc.osScope = pszScope;
    1243        1298 :                 m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
    1244         649 :                     .push_back(oDesc);
    1245             :             }
    1246             :         }
    1247             :     }
    1248             : 
    1249         703 :     return m_oMapTableToExtensions;
    1250             : }
    1251             : 
    1252             : /************************************************************************/
    1253             : /*                           GetContents()                              */
    1254             : /************************************************************************/
    1255             : 
    1256             : const std::map<CPLString, GPKGContentsDesc> &
    1257         807 : GDALGeoPackageDataset::GetContents()
    1258             : {
    1259         807 :     if (m_bMapTableToContentsBuilt)
    1260          68 :         return m_oMapTableToContents;
    1261         739 :     m_bMapTableToContentsBuilt = true;
    1262             : 
    1263             :     CPLString osSQL("SELECT table_name, data_type, identifier, "
    1264             :                     "description, min_x, min_y, max_x, max_y "
    1265        1478 :                     "FROM gpkg_contents");
    1266         739 :     const int nTableLimit = GetOGRTableLimit();
    1267         739 :     if (nTableLimit > 0)
    1268             :     {
    1269         739 :         osSQL += " LIMIT ";
    1270         739 :         osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1271             :     }
    1272             : 
    1273         739 :     auto oResult = SQLQuery(hDB, osSQL);
    1274         739 :     if (oResult)
    1275             :     {
    1276        1589 :         for (int i = 0; i < oResult->RowCount(); i++)
    1277             :         {
    1278         850 :             const char *pszTableName = oResult->GetValue(0, i);
    1279         850 :             if (pszTableName == nullptr)
    1280           0 :                 continue;
    1281         850 :             const char *pszDataType = oResult->GetValue(1, i);
    1282         850 :             const char *pszIdentifier = oResult->GetValue(2, i);
    1283         850 :             const char *pszDescription = oResult->GetValue(3, i);
    1284         850 :             const char *pszMinX = oResult->GetValue(4, i);
    1285         850 :             const char *pszMinY = oResult->GetValue(5, i);
    1286         850 :             const char *pszMaxX = oResult->GetValue(6, i);
    1287         850 :             const char *pszMaxY = oResult->GetValue(7, i);
    1288         850 :             GPKGContentsDesc oDesc;
    1289         850 :             if (pszDataType)
    1290         850 :                 oDesc.osDataType = pszDataType;
    1291         850 :             if (pszIdentifier)
    1292         850 :                 oDesc.osIdentifier = pszIdentifier;
    1293         850 :             if (pszDescription)
    1294         849 :                 oDesc.osDescription = pszDescription;
    1295         850 :             if (pszMinX)
    1296         578 :                 oDesc.osMinX = pszMinX;
    1297         850 :             if (pszMinY)
    1298         578 :                 oDesc.osMinY = pszMinY;
    1299         850 :             if (pszMaxX)
    1300         578 :                 oDesc.osMaxX = pszMaxX;
    1301         850 :             if (pszMaxY)
    1302         578 :                 oDesc.osMaxY = pszMaxY;
    1303        1700 :             m_oMapTableToContents[CPLString(pszTableName).toupper()] =
    1304        1700 :                 std::move(oDesc);
    1305             :         }
    1306             :     }
    1307             : 
    1308         739 :     return m_oMapTableToContents;
    1309             : }
    1310             : 
    1311             : /************************************************************************/
    1312             : /*                                Open()                                */
    1313             : /************************************************************************/
    1314             : 
    1315        1176 : int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
    1316             :                                 const std::string &osFilenameInZip)
    1317             : {
    1318        1176 :     m_osFilenameInZip = osFilenameInZip;
    1319        1176 :     CPLAssert(m_apoLayers.empty());
    1320        1176 :     CPLAssert(hDB == nullptr);
    1321             : 
    1322        1176 :     SetDescription(poOpenInfo->pszFilename);
    1323        2352 :     CPLString osFilename(poOpenInfo->pszFilename);
    1324        2352 :     CPLString osSubdatasetTableName;
    1325             :     GByte abyHeaderLetMeHerePlease[100];
    1326        1176 :     const GByte *pabyHeader = poOpenInfo->pabyHeader;
    1327        1176 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
    1328             :     {
    1329         242 :         char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
    1330             :                                                 CSLT_HONOURSTRINGS);
    1331         242 :         int nCount = CSLCount(papszTokens);
    1332         242 :         if (nCount < 2)
    1333             :         {
    1334           0 :             CSLDestroy(papszTokens);
    1335           0 :             return FALSE;
    1336             :         }
    1337             : 
    1338         242 :         if (nCount <= 3)
    1339             :         {
    1340         240 :             osFilename = papszTokens[1];
    1341             :         }
    1342             :         /* GPKG:C:\BLA.GPKG:foo */
    1343           2 :         else if (nCount == 4 && strlen(papszTokens[1]) == 1 &&
    1344           2 :                  (papszTokens[2][0] == '/' || papszTokens[2][0] == '\\'))
    1345             :         {
    1346           2 :             osFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
    1347             :         }
    1348             :         // GPKG:/vsicurl/http[s]://[user:passwd@]example.com[:8080]/foo.gpkg:bar
    1349           0 :         else if (/*nCount >= 4 && */
    1350           0 :                  (EQUAL(papszTokens[1], "/vsicurl/http") ||
    1351           0 :                   EQUAL(papszTokens[1], "/vsicurl/https")))
    1352             :         {
    1353           0 :             osFilename = CPLString(papszTokens[1]);
    1354           0 :             for (int i = 2; i < nCount - 1; i++)
    1355             :             {
    1356           0 :                 osFilename += ':';
    1357           0 :                 osFilename += papszTokens[i];
    1358             :             }
    1359             :         }
    1360         242 :         if (nCount >= 3)
    1361          14 :             osSubdatasetTableName = papszTokens[nCount - 1];
    1362             : 
    1363         242 :         CSLDestroy(papszTokens);
    1364         242 :         VSILFILE *fp = VSIFOpenL(osFilename, "rb");
    1365         242 :         if (fp != nullptr)
    1366             :         {
    1367         242 :             VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
    1368         242 :             VSIFCloseL(fp);
    1369             :         }
    1370         242 :         pabyHeader = abyHeaderLetMeHerePlease;
    1371             :     }
    1372         934 :     else if (poOpenInfo->pabyHeader &&
    1373         934 :              STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1374             :                          "SQLite format 3"))
    1375             :     {
    1376         928 :         m_bCallUndeclareFileNotToOpen = true;
    1377         928 :         GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
    1378             :                                          poOpenInfo->nHeaderBytes);
    1379             :     }
    1380             : 
    1381        1176 :     eAccess = poOpenInfo->eAccess;
    1382        1176 :     if (!m_osFilenameInZip.empty())
    1383             :     {
    1384           1 :         m_pszFilename = CPLStrdup(CPLSPrintf(
    1385             :             "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
    1386             :     }
    1387             :     else
    1388             :     {
    1389        1175 :         m_pszFilename = CPLStrdup(osFilename);
    1390             :     }
    1391             : 
    1392        1176 :     if (poOpenInfo->papszOpenOptions)
    1393             :     {
    1394         100 :         CSLDestroy(papszOpenOptions);
    1395         100 :         papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    1396             :     }
    1397             : 
    1398             : #ifdef ENABLE_SQL_GPKG_FORMAT
    1399        1176 :     if (poOpenInfo->pabyHeader &&
    1400         934 :         STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1401           5 :                     "-- SQL GPKG") &&
    1402           5 :         poOpenInfo->fpL != nullptr)
    1403             :     {
    1404           5 :         if (sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr) !=
    1405             :             SQLITE_OK)
    1406             :         {
    1407           0 :             return FALSE;
    1408             :         }
    1409             : 
    1410           5 :         InstallSQLFunctions();
    1411             : 
    1412             :         // Ingest the lines of the dump
    1413           5 :         VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
    1414             :         const char *pszLine;
    1415          76 :         while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
    1416             :         {
    1417          71 :             if (STARTS_WITH(pszLine, "--"))
    1418           5 :                 continue;
    1419             : 
    1420          66 :             if (!SQLCheckLineIsSafe(pszLine))
    1421           0 :                 return false;
    1422             : 
    1423          66 :             char *pszErrMsg = nullptr;
    1424          66 :             if (sqlite3_exec(hDB, pszLine, nullptr, nullptr, &pszErrMsg) !=
    1425             :                 SQLITE_OK)
    1426             :             {
    1427           0 :                 if (pszErrMsg)
    1428           0 :                     CPLDebug("SQLITE", "Error %s", pszErrMsg);
    1429             :             }
    1430          66 :             sqlite3_free(pszErrMsg);
    1431           5 :         }
    1432             :     }
    1433             : 
    1434        1171 :     else if (pabyHeader != nullptr)
    1435             : #endif
    1436             :     {
    1437        1171 :         if (poOpenInfo->fpL)
    1438             :         {
    1439             :             // See above comment about -wal locking for the importance of
    1440             :             // closing that file, prior to calling sqlite3_open()
    1441         829 :             VSIFCloseL(poOpenInfo->fpL);
    1442         829 :             poOpenInfo->fpL = nullptr;
    1443             :         }
    1444             : 
    1445             :         /* See if we can open the SQLite database */
    1446        1171 :         if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
    1447             :                                         : SQLITE_OPEN_READONLY))
    1448           2 :             return FALSE;
    1449             : 
    1450        1169 :         memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
    1451        1169 :         m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
    1452        1169 :         memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
    1453        1169 :         m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
    1454        1169 :         if (m_nApplicationId == GP10_APPLICATION_ID)
    1455             :         {
    1456           7 :             CPLDebug("GPKG", "GeoPackage v1.0");
    1457             :         }
    1458        1162 :         else if (m_nApplicationId == GP11_APPLICATION_ID)
    1459             :         {
    1460           2 :             CPLDebug("GPKG", "GeoPackage v1.1");
    1461             :         }
    1462        1160 :         else if (m_nApplicationId == GPKG_APPLICATION_ID &&
    1463        1157 :                  m_nUserVersion >= GPKG_1_2_VERSION)
    1464             :         {
    1465        1155 :             CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
    1466        1155 :                      (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
    1467             :         }
    1468             :     }
    1469             : 
    1470             :     /* Requirement 6: The SQLite PRAGMA integrity_check SQL command SHALL return
    1471             :      * “ok” */
    1472             :     /* http://opengis.github.io/geopackage/#_file_integrity */
    1473             :     /* Disable integrity check by default, since it is expensive on big files */
    1474        1174 :     if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_INTEGRITY_CHECK", "NO")) &&
    1475           0 :         OGRERR_NONE != PragmaCheck("integrity_check", "ok", 1))
    1476             :     {
    1477           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1478             :                  "pragma integrity_check on '%s' failed", m_pszFilename);
    1479           0 :         return FALSE;
    1480             :     }
    1481             : 
    1482             :     /* Requirement 7: The SQLite PRAGMA foreign_key_check() SQL with no */
    1483             :     /* parameter value SHALL return an empty result set */
    1484             :     /* http://opengis.github.io/geopackage/#_file_integrity */
    1485             :     /* Disable the check by default, since it is to corrupt databases, and */
    1486             :     /* that causes issues to downstream software that can't open them. */
    1487        1174 :     if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_FOREIGN_KEY_CHECK", "NO")) &&
    1488           0 :         OGRERR_NONE != PragmaCheck("foreign_key_check", "", 0))
    1489             :     {
    1490           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1491             :                  "pragma foreign_key_check on '%s' failed.", m_pszFilename);
    1492           0 :         return FALSE;
    1493             :     }
    1494             : 
    1495             :     /* Check for requirement metadata tables */
    1496             :     /* Requirement 10: gpkg_spatial_ref_sys must exist */
    1497             :     /* Requirement 13: gpkg_contents must exist */
    1498        1174 :     if (SQLGetInteger(hDB,
    1499             :                       "SELECT COUNT(*) FROM sqlite_master WHERE "
    1500             :                       "name IN ('gpkg_spatial_ref_sys', 'gpkg_contents') AND "
    1501             :                       "type IN ('table', 'view')",
    1502        1174 :                       nullptr) != 2)
    1503             :     {
    1504           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1505             :                  "At least one of the required GeoPackage tables, "
    1506             :                  "gpkg_spatial_ref_sys or gpkg_contents, is missing");
    1507           0 :         return FALSE;
    1508             :     }
    1509             : 
    1510        1174 :     DetectSpatialRefSysColumns();
    1511             : 
    1512             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    1513        1174 :     if (SQLGetInteger(hDB,
    1514             :                       "SELECT 1 FROM sqlite_master WHERE "
    1515             :                       "name = 'gpkg_ogr_contents' AND type = 'table'",
    1516        1174 :                       nullptr) == 1)
    1517             :     {
    1518        1166 :         m_bHasGPKGOGRContents = true;
    1519             :     }
    1520             : #endif
    1521             : 
    1522        1174 :     CheckUnknownExtensions();
    1523             : 
    1524        1174 :     int bRet = FALSE;
    1525        1174 :     bool bHasGPKGExtRelations = false;
    1526        1174 :     if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
    1527             :     {
    1528         987 :         m_bHasGPKGGeometryColumns =
    1529         987 :             SQLGetInteger(hDB,
    1530             :                           "SELECT 1 FROM sqlite_master WHERE "
    1531             :                           "name = 'gpkg_geometry_columns' AND "
    1532             :                           "type IN ('table', 'view')",
    1533         987 :                           nullptr) == 1;
    1534         987 :         bHasGPKGExtRelations = HasGpkgextRelationsTable();
    1535             :     }
    1536        1174 :     if (m_bHasGPKGGeometryColumns)
    1537             :     {
    1538             :         /* Load layer definitions for all tables in gpkg_contents &
    1539             :          * gpkg_geometry_columns */
    1540             :         /* and non-spatial tables as well */
    1541             :         std::string osSQL =
    1542             :             "SELECT c.table_name, c.identifier, 1 as is_spatial, "
    1543             :             "g.column_name, g.geometry_type_name, g.z, g.m, c.min_x, c.min_y, "
    1544             :             "c.max_x, c.max_y, 1 AS is_in_gpkg_contents, "
    1545             :             "(SELECT type FROM sqlite_master WHERE lower(name) = "
    1546             :             "lower(c.table_name) AND type IN ('table', 'view')) AS object_type "
    1547             :             "  FROM gpkg_geometry_columns g "
    1548             :             "  JOIN gpkg_contents c ON (g.table_name = c.table_name)"
    1549             :             "  WHERE "
    1550             :             "  c.table_name <> 'ogr_empty_table' AND"
    1551             :             "  c.data_type = 'features' "
    1552             :             // aspatial: Was the only method available in OGR 2.0 and 2.1
    1553             :             // attributes: GPKG 1.2 or later
    1554             :             "UNION ALL "
    1555             :             "SELECT table_name, identifier, 0 as is_spatial, NULL, NULL, 0, 0, "
    1556             :             "0 AS xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 1 AS "
    1557             :             "is_in_gpkg_contents, "
    1558             :             "(SELECT type FROM sqlite_master WHERE lower(name) = "
    1559             :             "lower(table_name) AND type IN ('table', 'view')) AS object_type "
    1560             :             "  FROM gpkg_contents"
    1561         986 :             "  WHERE data_type IN ('aspatial', 'attributes') ";
    1562             : 
    1563        1972 :         const char *pszListAllTables = CSLFetchNameValueDef(
    1564         986 :             poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
    1565         986 :         bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
    1566         986 :         if (!bHasASpatialOrAttributes)
    1567             :         {
    1568             :             auto oResultTable =
    1569             :                 SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
    1570         985 :                               "data_type = 'attributes' LIMIT 1");
    1571         985 :             bHasASpatialOrAttributes =
    1572         985 :                 (oResultTable && oResultTable->RowCount() == 1);
    1573             :         }
    1574         986 :         if (bHasGPKGExtRelations)
    1575             :         {
    1576             :             osSQL += "UNION ALL "
    1577             :                      "SELECT mapping_table_name, mapping_table_name, 0 as "
    1578             :                      "is_spatial, NULL, NULL, 0, 0, 0 AS "
    1579             :                      "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
    1580             :                      "is_in_gpkg_contents, 'table' AS object_type "
    1581             :                      "FROM gpkgext_relations WHERE "
    1582             :                      "lower(mapping_table_name) NOT IN (SELECT "
    1583             :                      "lower(table_name) FROM gpkg_contents) AND "
    1584             :                      "EXISTS (SELECT 1 FROM sqlite_master WHERE "
    1585             :                      "type IN ('table', 'view') AND "
    1586          18 :                      "lower(name) = lower(mapping_table_name))";
    1587             :         }
    1588         986 :         if (EQUAL(pszListAllTables, "YES") ||
    1589         985 :             (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
    1590             :         {
    1591             :             // vgpkg_ is Spatialite virtual table
    1592             :             osSQL +=
    1593             :                 "UNION ALL "
    1594             :                 "SELECT name, name, 0 as is_spatial, NULL, NULL, 0, 0, 0 AS "
    1595             :                 "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
    1596             :                 "is_in_gpkg_contents, type AS object_type "
    1597             :                 "FROM sqlite_master WHERE type IN ('table', 'view') "
    1598             :                 "AND name NOT LIKE 'gpkg_%' "
    1599             :                 "AND name NOT LIKE 'vgpkg_%' "
    1600             :                 "AND name NOT LIKE 'rtree_%' AND name NOT LIKE 'sqlite_%' "
    1601             :                 // Avoid reading those views from simple_sewer_features.gpkg
    1602             :                 "AND name NOT IN ('st_spatial_ref_sys', 'spatial_ref_sys', "
    1603             :                 "'st_geometry_columns', 'geometry_columns') "
    1604             :                 "AND lower(name) NOT IN (SELECT lower(table_name) FROM "
    1605         927 :                 "gpkg_contents)";
    1606         927 :             if (bHasGPKGExtRelations)
    1607             :             {
    1608             :                 osSQL += " AND lower(name) NOT IN (SELECT "
    1609             :                          "lower(mapping_table_name) FROM "
    1610          13 :                          "gpkgext_relations)";
    1611             :             }
    1612             :         }
    1613         986 :         const int nTableLimit = GetOGRTableLimit();
    1614         986 :         if (nTableLimit > 0)
    1615             :         {
    1616         986 :             osSQL += " LIMIT ";
    1617         986 :             osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1618             :         }
    1619             : 
    1620         986 :         auto oResult = SQLQuery(hDB, osSQL.c_str());
    1621         986 :         if (!oResult)
    1622             :         {
    1623           0 :             return FALSE;
    1624             :         }
    1625             : 
    1626         986 :         if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
    1627             :         {
    1628           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1629             :                      "File has more than %d vector tables. "
    1630             :                      "Limiting to first %d (can be overridden with "
    1631             :                      "OGR_TABLE_LIMIT config option)",
    1632             :                      nTableLimit, nTableLimit);
    1633           1 :             oResult->LimitRowCount(nTableLimit);
    1634             :         }
    1635             : 
    1636         986 :         if (oResult->RowCount() > 0)
    1637             :         {
    1638         870 :             bRet = TRUE;
    1639             : 
    1640         870 :             m_apoLayers.reserve(oResult->RowCount());
    1641             : 
    1642        1740 :             std::map<std::string, int> oMapTableRefCount;
    1643        3922 :             for (int i = 0; i < oResult->RowCount(); i++)
    1644             :             {
    1645        3052 :                 const char *pszTableName = oResult->GetValue(0, i);
    1646        3052 :                 if (pszTableName == nullptr)
    1647           0 :                     continue;
    1648        3052 :                 if (++oMapTableRefCount[pszTableName] == 2)
    1649             :                 {
    1650             :                     // This should normally not happen if all constraints are
    1651             :                     // properly set
    1652           2 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1653             :                              "Table %s appearing several times in "
    1654             :                              "gpkg_contents and/or gpkg_geometry_columns",
    1655             :                              pszTableName);
    1656             :                 }
    1657             :             }
    1658             : 
    1659        1740 :             std::set<std::string> oExistingLayers;
    1660        3922 :             for (int i = 0; i < oResult->RowCount(); i++)
    1661             :             {
    1662        3052 :                 const char *pszTableName = oResult->GetValue(0, i);
    1663        3052 :                 if (pszTableName == nullptr)
    1664           2 :                     continue;
    1665             :                 const bool bTableHasSeveralGeomColumns =
    1666        3052 :                     oMapTableRefCount[pszTableName] > 1;
    1667        3052 :                 bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
    1668        3052 :                 const char *pszGeomColName = oResult->GetValue(3, i);
    1669        3052 :                 const char *pszGeomType = oResult->GetValue(4, i);
    1670        3052 :                 const char *pszZ = oResult->GetValue(5, i);
    1671        3052 :                 const char *pszM = oResult->GetValue(6, i);
    1672             :                 bool bIsInGpkgContents =
    1673        3052 :                     CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
    1674        3052 :                 if (!bIsInGpkgContents)
    1675          44 :                     m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
    1676        3052 :                 const char *pszObjectType = oResult->GetValue(12, i);
    1677        3052 :                 if (pszObjectType == nullptr ||
    1678        3051 :                     !(EQUAL(pszObjectType, "table") ||
    1679          21 :                       EQUAL(pszObjectType, "view")))
    1680             :                 {
    1681           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1682             :                              "Table/view %s is referenced in gpkg_contents, "
    1683             :                              "but does not exist",
    1684             :                              pszTableName);
    1685           1 :                     continue;
    1686             :                 }
    1687             :                 // Non-standard and undocumented behavior:
    1688             :                 // if the same table appears to have several geometry columns,
    1689             :                 // handle it for now as multiple layers named
    1690             :                 // "table_name (geom_col_name)"
    1691             :                 // The way we handle that might change in the future (e.g
    1692             :                 // could be a single layer with multiple geometry columns)
    1693             :                 const std::string osLayerNameWithGeomColName =
    1694        5886 :                     pszGeomColName ? std::string(pszTableName) + " (" +
    1695             :                                          pszGeomColName + ')'
    1696        6102 :                                    : std::string(pszTableName);
    1697        3051 :                 if (cpl::contains(oExistingLayers, osLayerNameWithGeomColName))
    1698           1 :                     continue;
    1699        3050 :                 oExistingLayers.insert(osLayerNameWithGeomColName);
    1700             :                 const std::string osLayerName = bTableHasSeveralGeomColumns
    1701             :                                                     ? osLayerNameWithGeomColName
    1702        6100 :                                                     : std::string(pszTableName);
    1703             :                 auto poLayer = std::make_unique<OGRGeoPackageTableLayer>(
    1704        6100 :                     this, osLayerName.c_str());
    1705        3050 :                 bool bHasZ = pszZ && atoi(pszZ) > 0;
    1706        3050 :                 bool bHasM = pszM && atoi(pszM) > 0;
    1707        3050 :                 if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
    1708             :                 {
    1709         604 :                     if (pszZ && atoi(pszZ) == 2)
    1710           7 :                         bHasZ = false;
    1711         604 :                     if (pszM && atoi(pszM) == 2)
    1712           6 :                         bHasM = false;
    1713             :                 }
    1714        3050 :                 poLayer->SetOpeningParameters(
    1715             :                     pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
    1716             :                     pszGeomColName, pszGeomType, bHasZ, bHasM);
    1717        3050 :                 m_apoLayers.push_back(std::move(poLayer));
    1718             :             }
    1719             :         }
    1720             :     }
    1721             : 
    1722        1174 :     bool bHasTileMatrixSet = false;
    1723        1174 :     if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
    1724             :     {
    1725         560 :         bHasTileMatrixSet = SQLGetInteger(hDB,
    1726             :                                           "SELECT 1 FROM sqlite_master WHERE "
    1727             :                                           "name = 'gpkg_tile_matrix_set' AND "
    1728             :                                           "type IN ('table', 'view')",
    1729             :                                           nullptr) == 1;
    1730             :     }
    1731        1174 :     if (bHasTileMatrixSet)
    1732             :     {
    1733             :         std::string osSQL =
    1734             :             "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
    1735             :             "c.min_x, c.min_y, c.max_x, c.max_y, "
    1736             :             "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
    1737             :             "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
    1738             :             "c.table_name = tms.table_name WHERE "
    1739         559 :             "data_type IN ('tiles', '2d-gridded-coverage')";
    1740         559 :         if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
    1741             :             osSubdatasetTableName =
    1742           2 :                 CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
    1743         559 :         if (!osSubdatasetTableName.empty())
    1744             :         {
    1745          16 :             char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
    1746             :                                            osSubdatasetTableName.c_str());
    1747          16 :             osSQL += pszTmp;
    1748          16 :             sqlite3_free(pszTmp);
    1749          16 :             SetPhysicalFilename(osFilename.c_str());
    1750             :         }
    1751         559 :         const int nTableLimit = GetOGRTableLimit();
    1752         559 :         if (nTableLimit > 0)
    1753             :         {
    1754         559 :             osSQL += " LIMIT ";
    1755         559 :             osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1756             :         }
    1757             : 
    1758         559 :         auto oResult = SQLQuery(hDB, osSQL.c_str());
    1759         559 :         if (!oResult)
    1760             :         {
    1761           0 :             return FALSE;
    1762             :         }
    1763             : 
    1764         559 :         if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
    1765             :         {
    1766           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1767             :                      "Cannot find table '%s' in GeoPackage dataset",
    1768             :                      osSubdatasetTableName.c_str());
    1769             :         }
    1770         558 :         else if (oResult->RowCount() == 1)
    1771             :         {
    1772         274 :             const char *pszTableName = oResult->GetValue(0, 0);
    1773         274 :             const char *pszIdentifier = oResult->GetValue(1, 0);
    1774         274 :             const char *pszDescription = oResult->GetValue(2, 0);
    1775         274 :             const char *pszSRSId = oResult->GetValue(3, 0);
    1776         274 :             const char *pszMinX = oResult->GetValue(4, 0);
    1777         274 :             const char *pszMinY = oResult->GetValue(5, 0);
    1778         274 :             const char *pszMaxX = oResult->GetValue(6, 0);
    1779         274 :             const char *pszMaxY = oResult->GetValue(7, 0);
    1780         274 :             const char *pszTMSMinX = oResult->GetValue(8, 0);
    1781         274 :             const char *pszTMSMinY = oResult->GetValue(9, 0);
    1782         274 :             const char *pszTMSMaxX = oResult->GetValue(10, 0);
    1783         274 :             const char *pszTMSMaxY = oResult->GetValue(11, 0);
    1784         274 :             const char *pszDataType = oResult->GetValue(12, 0);
    1785         274 :             if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
    1786             :                 pszTMSMaxY)
    1787             :             {
    1788         548 :                 bRet = OpenRaster(
    1789             :                     pszTableName, pszIdentifier, pszDescription,
    1790         274 :                     pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
    1791             :                     CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
    1792             :                     CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
    1793         274 :                     EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
    1794             :             }
    1795             :         }
    1796         284 :         else if (oResult->RowCount() >= 1)
    1797             :         {
    1798           5 :             bRet = TRUE;
    1799             : 
    1800           5 :             if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
    1801             :             {
    1802           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1803             :                          "File has more than %d raster tables. "
    1804             :                          "Limiting to first %d (can be overridden with "
    1805             :                          "OGR_TABLE_LIMIT config option)",
    1806             :                          nTableLimit, nTableLimit);
    1807           1 :                 oResult->LimitRowCount(nTableLimit);
    1808             :             }
    1809             : 
    1810           5 :             int nSDSCount = 0;
    1811        2013 :             for (int i = 0; i < oResult->RowCount(); i++)
    1812             :             {
    1813        2008 :                 const char *pszTableName = oResult->GetValue(0, i);
    1814        2008 :                 const char *pszIdentifier = oResult->GetValue(1, i);
    1815        2008 :                 if (pszTableName == nullptr)
    1816           0 :                     continue;
    1817             :                 m_aosSubDatasets.AddNameValue(
    1818             :                     CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
    1819        2008 :                     CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
    1820             :                 m_aosSubDatasets.AddNameValue(
    1821             :                     CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
    1822             :                     pszIdentifier
    1823        2008 :                         ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
    1824        4016 :                         : pszTableName);
    1825        2008 :                 nSDSCount++;
    1826             :             }
    1827             :         }
    1828             :     }
    1829             : 
    1830        1174 :     if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
    1831             :     {
    1832          32 :         if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
    1833             :         {
    1834          21 :             bRet = TRUE;
    1835             :         }
    1836             :         else
    1837             :         {
    1838          11 :             CPLDebug("GPKG",
    1839             :                      "This GeoPackage has no vector content and is opened "
    1840             :                      "in read-only mode. If you open it in update mode, "
    1841             :                      "opening will be successful.");
    1842             :         }
    1843             :     }
    1844             : 
    1845        1174 :     if (eAccess == GA_Update)
    1846             :     {
    1847         225 :         FixupWrongRTreeTrigger();
    1848         225 :         FixupWrongMedataReferenceColumnNameUpdate();
    1849             :     }
    1850             : 
    1851        1174 :     SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    1852             : 
    1853        1174 :     return bRet;
    1854             : }
    1855             : 
    1856             : /************************************************************************/
    1857             : /*                    DetectSpatialRefSysColumns()                      */
    1858             : /************************************************************************/
    1859             : 
    1860        1184 : void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
    1861             : {
    1862             :     // Detect definition_12_063 column
    1863             :     {
    1864        1184 :         sqlite3_stmt *hSQLStmt = nullptr;
    1865        1184 :         int rc = sqlite3_prepare_v2(
    1866             :             hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
    1867             :             &hSQLStmt, nullptr);
    1868        1184 :         if (rc == SQLITE_OK)
    1869             :         {
    1870          85 :             m_bHasDefinition12_063 = true;
    1871          85 :             sqlite3_finalize(hSQLStmt);
    1872             :         }
    1873             :     }
    1874             : 
    1875             :     // Detect epoch column
    1876        1184 :     if (m_bHasDefinition12_063)
    1877             :     {
    1878          85 :         sqlite3_stmt *hSQLStmt = nullptr;
    1879             :         int rc =
    1880          85 :             sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
    1881             :                                -1, &hSQLStmt, nullptr);
    1882          85 :         if (rc == SQLITE_OK)
    1883             :         {
    1884          76 :             m_bHasEpochColumn = true;
    1885          76 :             sqlite3_finalize(hSQLStmt);
    1886             :         }
    1887             :     }
    1888        1184 : }
    1889             : 
    1890             : /************************************************************************/
    1891             : /*                    FixupWrongRTreeTrigger()                          */
    1892             : /************************************************************************/
    1893             : 
    1894         225 : void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
    1895             : {
    1896             :     auto oResult = SQLQuery(
    1897             :         hDB,
    1898             :         "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
    1899         225 :         "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
    1900         225 :     if (oResult == nullptr)
    1901           0 :         return;
    1902         225 :     if (oResult->RowCount() > 0)
    1903             :     {
    1904           1 :         CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
    1905             :     }
    1906         227 :     for (int i = 0; i < oResult->RowCount(); i++)
    1907             :     {
    1908           2 :         const char *pszName = oResult->GetValue(0, i);
    1909           2 :         const char *pszSQL = oResult->GetValue(1, i);
    1910           2 :         const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
    1911           2 :         if (pszPtr1)
    1912             :         {
    1913           2 :             const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
    1914             :             // Skipping over geometry column name
    1915           4 :             while (*pszPtr == ' ')
    1916           2 :                 pszPtr++;
    1917           2 :             if (pszPtr[0] == '"' || pszPtr[0] == '\'')
    1918             :             {
    1919           1 :                 char chStringDelim = pszPtr[0];
    1920           1 :                 pszPtr++;
    1921           9 :                 while (*pszPtr != '\0' && *pszPtr != chStringDelim)
    1922             :                 {
    1923           8 :                     if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
    1924           0 :                         pszPtr += 2;
    1925             :                     else
    1926           8 :                         pszPtr += 1;
    1927             :                 }
    1928           1 :                 if (*pszPtr == chStringDelim)
    1929           1 :                     pszPtr++;
    1930             :             }
    1931             :             else
    1932             :             {
    1933           1 :                 pszPtr++;
    1934           8 :                 while (*pszPtr != ' ')
    1935           7 :                     pszPtr++;
    1936             :             }
    1937           2 :             if (*pszPtr == ' ')
    1938             :             {
    1939           2 :                 SQLCommand(hDB,
    1940           4 :                            ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
    1941             :                                .c_str());
    1942           4 :                 CPLString newSQL;
    1943           2 :                 newSQL.assign(pszSQL, pszPtr1 - pszSQL);
    1944           2 :                 newSQL += " AFTER UPDATE";
    1945           2 :                 newSQL += pszPtr;
    1946           2 :                 SQLCommand(hDB, newSQL);
    1947             :             }
    1948             :         }
    1949             :     }
    1950             : }
    1951             : 
    1952             : /************************************************************************/
    1953             : /*             FixupWrongMedataReferenceColumnNameUpdate()              */
    1954             : /************************************************************************/
    1955             : 
    1956         225 : void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
    1957             : {
    1958             :     // Fix wrong trigger that was generated by GDAL < 2.4.0
    1959             :     // See https://github.com/qgis/QGIS/issues/42768
    1960             :     auto oResult = SQLQuery(
    1961             :         hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
    1962             :              "NAME ='gpkg_metadata_reference_column_name_update' AND "
    1963         225 :              "sql LIKE '%column_nameIS%'");
    1964         225 :     if (oResult == nullptr)
    1965           0 :         return;
    1966         225 :     if (oResult->RowCount() == 1)
    1967             :     {
    1968           1 :         CPLDebug("GPKG", "Fixing incorrect trigger "
    1969             :                          "gpkg_metadata_reference_column_name_update");
    1970           1 :         const char *pszSQL = oResult->GetValue(0, 0);
    1971             :         std::string osNewSQL(
    1972           3 :             CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
    1973             : 
    1974           1 :         SQLCommand(hDB,
    1975             :                    "DROP TRIGGER gpkg_metadata_reference_column_name_update");
    1976           1 :         SQLCommand(hDB, osNewSQL.c_str());
    1977             :     }
    1978             : }
    1979             : 
    1980             : /************************************************************************/
    1981             : /*                  ClearCachedRelationships()                          */
    1982             : /************************************************************************/
    1983             : 
    1984          36 : void GDALGeoPackageDataset::ClearCachedRelationships()
    1985             : {
    1986          36 :     m_bHasPopulatedRelationships = false;
    1987          36 :     m_osMapRelationships.clear();
    1988          36 : }
    1989             : 
    1990             : /************************************************************************/
    1991             : /*                           LoadRelationships()                        */
    1992             : /************************************************************************/
    1993             : 
    1994          82 : void GDALGeoPackageDataset::LoadRelationships() const
    1995             : {
    1996          82 :     m_osMapRelationships.clear();
    1997             : 
    1998          82 :     std::vector<std::string> oExcludedTables;
    1999          82 :     if (HasGpkgextRelationsTable())
    2000             :     {
    2001          37 :         LoadRelationshipsUsingRelatedTablesExtension();
    2002             : 
    2003          89 :         for (const auto &oRelationship : m_osMapRelationships)
    2004             :         {
    2005             :             oExcludedTables.emplace_back(
    2006          52 :                 oRelationship.second->GetMappingTableName());
    2007             :         }
    2008             :     }
    2009             : 
    2010             :     // Also load relationships defined using foreign keys (i.e. one-to-many
    2011             :     // relationships). Here we must exclude any relationships defined from the
    2012             :     // related tables extension, we don't want them included twice.
    2013          82 :     LoadRelationshipsFromForeignKeys(oExcludedTables);
    2014          82 :     m_bHasPopulatedRelationships = true;
    2015          82 : }
    2016             : 
    2017             : /************************************************************************/
    2018             : /*         LoadRelationshipsUsingRelatedTablesExtension()               */
    2019             : /************************************************************************/
    2020             : 
    2021          37 : void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
    2022             : {
    2023          37 :     m_osMapRelationships.clear();
    2024             : 
    2025             :     auto oResultTable = SQLQuery(
    2026          37 :         hDB, "SELECT base_table_name, base_primary_column, "
    2027             :              "related_table_name, related_primary_column, relation_name, "
    2028          74 :              "mapping_table_name FROM gpkgext_relations");
    2029          37 :     if (oResultTable && oResultTable->RowCount() > 0)
    2030             :     {
    2031          86 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    2032             :         {
    2033          53 :             const char *pszBaseTableName = oResultTable->GetValue(0, i);
    2034          53 :             if (!pszBaseTableName)
    2035             :             {
    2036           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2037             :                          "Could not retrieve base_table_name from "
    2038             :                          "gpkgext_relations");
    2039           1 :                 continue;
    2040             :             }
    2041          53 :             const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
    2042          53 :             if (!pszBasePrimaryColumn)
    2043             :             {
    2044           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2045             :                          "Could not retrieve base_primary_column from "
    2046             :                          "gpkgext_relations");
    2047           0 :                 continue;
    2048             :             }
    2049          53 :             const char *pszRelatedTableName = oResultTable->GetValue(2, i);
    2050          53 :             if (!pszRelatedTableName)
    2051             :             {
    2052           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2053             :                          "Could not retrieve related_table_name from "
    2054             :                          "gpkgext_relations");
    2055           0 :                 continue;
    2056             :             }
    2057          53 :             const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
    2058          53 :             if (!pszRelatedPrimaryColumn)
    2059             :             {
    2060           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2061             :                          "Could not retrieve related_primary_column from "
    2062             :                          "gpkgext_relations");
    2063           0 :                 continue;
    2064             :             }
    2065          53 :             const char *pszRelationName = oResultTable->GetValue(4, i);
    2066          53 :             if (!pszRelationName)
    2067             :             {
    2068           0 :                 CPLError(
    2069             :                     CE_Warning, CPLE_AppDefined,
    2070             :                     "Could not retrieve relation_name from gpkgext_relations");
    2071           0 :                 continue;
    2072             :             }
    2073          53 :             const char *pszMappingTableName = oResultTable->GetValue(5, i);
    2074          53 :             if (!pszMappingTableName)
    2075             :             {
    2076           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2077             :                          "Could not retrieve mapping_table_name from "
    2078             :                          "gpkgext_relations");
    2079           0 :                 continue;
    2080             :             }
    2081             : 
    2082             :             // confirm that mapping table exists
    2083             :             char *pszSQL =
    2084          53 :                 sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
    2085             :                                 "name='%q' AND type IN ('table', 'view')",
    2086             :                                 pszMappingTableName);
    2087          53 :             const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
    2088          53 :             sqlite3_free(pszSQL);
    2089             : 
    2090          55 :             if (nMappingTableCount < 1 &&
    2091           2 :                 !const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
    2092           2 :                     pszMappingTableName))
    2093             :             {
    2094           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2095             :                          "Relationship mapping table %s does not exist",
    2096             :                          pszMappingTableName);
    2097           1 :                 continue;
    2098             :             }
    2099             : 
    2100             :             const std::string osRelationName = GenerateNameForRelationship(
    2101         104 :                 pszBaseTableName, pszRelatedTableName, pszRelationName);
    2102             : 
    2103         104 :             std::string osType{};
    2104             :             // defined requirement classes -- for these types the relation name
    2105             :             // will be specific string value from the related tables extension.
    2106             :             // In this case we need to construct a unique relationship name
    2107             :             // based on the related tables
    2108          52 :             if (EQUAL(pszRelationName, "media") ||
    2109          40 :                 EQUAL(pszRelationName, "simple_attributes") ||
    2110          40 :                 EQUAL(pszRelationName, "features") ||
    2111          18 :                 EQUAL(pszRelationName, "attributes") ||
    2112           2 :                 EQUAL(pszRelationName, "tiles"))
    2113             :             {
    2114          50 :                 osType = pszRelationName;
    2115             :             }
    2116             :             else
    2117             :             {
    2118             :                 // user defined types default to features
    2119           2 :                 osType = "features";
    2120             :             }
    2121             : 
    2122             :             auto poRelationship = std::make_unique<GDALRelationship>(
    2123             :                 osRelationName, pszBaseTableName, pszRelatedTableName,
    2124         104 :                 GRC_MANY_TO_MANY);
    2125             : 
    2126         104 :             poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
    2127         104 :             poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
    2128         104 :             poRelationship->SetLeftMappingTableFields({"base_id"});
    2129         104 :             poRelationship->SetRightMappingTableFields({"related_id"});
    2130          52 :             poRelationship->SetMappingTableName(pszMappingTableName);
    2131          52 :             poRelationship->SetRelatedTableType(osType);
    2132             : 
    2133          52 :             m_osMapRelationships[osRelationName] = std::move(poRelationship);
    2134             :         }
    2135             :     }
    2136          37 : }
    2137             : 
    2138             : /************************************************************************/
    2139             : /*                GenerateNameForRelationship()                         */
    2140             : /************************************************************************/
    2141             : 
    2142          76 : std::string GDALGeoPackageDataset::GenerateNameForRelationship(
    2143             :     const char *pszBaseTableName, const char *pszRelatedTableName,
    2144             :     const char *pszType)
    2145             : {
    2146             :     // defined requirement classes -- for these types the relation name will be
    2147             :     // specific string value from the related tables extension. In this case we
    2148             :     // need to construct a unique relationship name based on the related tables
    2149          76 :     if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
    2150          53 :         EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
    2151           8 :         EQUAL(pszType, "tiles"))
    2152             :     {
    2153         136 :         std::ostringstream stream;
    2154             :         stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
    2155          68 :                << pszType;
    2156          68 :         return stream.str();
    2157             :     }
    2158             :     else
    2159             :     {
    2160             :         // user defined types default to features
    2161           8 :         return pszType;
    2162             :     }
    2163             : }
    2164             : 
    2165             : /************************************************************************/
    2166             : /*                       ValidateRelationship()                         */
    2167             : /************************************************************************/
    2168             : 
    2169          28 : bool GDALGeoPackageDataset::ValidateRelationship(
    2170             :     const GDALRelationship *poRelationship, std::string &failureReason)
    2171             : {
    2172             : 
    2173          28 :     if (poRelationship->GetCardinality() !=
    2174             :         GDALRelationshipCardinality::GRC_MANY_TO_MANY)
    2175             :     {
    2176           3 :         failureReason = "Only many to many relationships are supported";
    2177           3 :         return false;
    2178             :     }
    2179             : 
    2180          50 :     std::string osRelatedTableType = poRelationship->GetRelatedTableType();
    2181          65 :     if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
    2182          30 :         osRelatedTableType != "media" &&
    2183          20 :         osRelatedTableType != "simple_attributes" &&
    2184          55 :         osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
    2185             :     {
    2186             :         failureReason =
    2187           4 :             ("Related table type " + osRelatedTableType +
    2188             :              " is not a valid value for the GeoPackage specification. "
    2189             :              "Valid values are: features, media, simple_attributes, "
    2190             :              "attributes, tiles.")
    2191           2 :                 .c_str();
    2192           2 :         return false;
    2193             :     }
    2194             : 
    2195          23 :     const std::string &osLeftTableName = poRelationship->GetLeftTableName();
    2196          23 :     OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
    2197          23 :         GetLayerByName(osLeftTableName.c_str()));
    2198          23 :     if (!poLeftTable)
    2199             :     {
    2200           4 :         failureReason = ("Left table " + osLeftTableName +
    2201             :                          " is not an existing layer in the dataset")
    2202           2 :                             .c_str();
    2203           2 :         return false;
    2204             :     }
    2205          21 :     const std::string &osRightTableName = poRelationship->GetRightTableName();
    2206          21 :     OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
    2207          21 :         GetLayerByName(osRightTableName.c_str()));
    2208          21 :     if (!poRightTable)
    2209             :     {
    2210           4 :         failureReason = ("Right table " + osRightTableName +
    2211             :                          " is not an existing layer in the dataset")
    2212           2 :                             .c_str();
    2213           2 :         return false;
    2214             :     }
    2215             : 
    2216          19 :     const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
    2217          19 :     if (aosLeftTableFields.empty())
    2218             :     {
    2219           1 :         failureReason = "No left table fields were specified";
    2220           1 :         return false;
    2221             :     }
    2222          18 :     else if (aosLeftTableFields.size() > 1)
    2223             :     {
    2224             :         failureReason = "Only a single left table field is permitted for the "
    2225           1 :                         "GeoPackage specification";
    2226           1 :         return false;
    2227             :     }
    2228             :     else
    2229             :     {
    2230             :         // validate left field exists
    2231          34 :         if (poLeftTable->GetLayerDefn()->GetFieldIndex(
    2232          37 :                 aosLeftTableFields[0].c_str()) < 0 &&
    2233           3 :             !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
    2234             :         {
    2235           2 :             failureReason = ("Left table field " + aosLeftTableFields[0] +
    2236           2 :                              " does not exist in " + osLeftTableName)
    2237           1 :                                 .c_str();
    2238           1 :             return false;
    2239             :         }
    2240             :     }
    2241             : 
    2242          16 :     const auto &aosRightTableFields = poRelationship->GetRightTableFields();
    2243          16 :     if (aosRightTableFields.empty())
    2244             :     {
    2245           1 :         failureReason = "No right table fields were specified";
    2246           1 :         return false;
    2247             :     }
    2248          15 :     else if (aosRightTableFields.size() > 1)
    2249             :     {
    2250             :         failureReason = "Only a single right table field is permitted for the "
    2251           1 :                         "GeoPackage specification";
    2252           1 :         return false;
    2253             :     }
    2254             :     else
    2255             :     {
    2256             :         // validate right field exists
    2257          28 :         if (poRightTable->GetLayerDefn()->GetFieldIndex(
    2258          32 :                 aosRightTableFields[0].c_str()) < 0 &&
    2259           4 :             !EQUAL(poRightTable->GetFIDColumn(),
    2260             :                    aosRightTableFields[0].c_str()))
    2261             :         {
    2262           4 :             failureReason = ("Right table field " + aosRightTableFields[0] +
    2263           4 :                              " does not exist in " + osRightTableName)
    2264           2 :                                 .c_str();
    2265           2 :             return false;
    2266             :         }
    2267             :     }
    2268             : 
    2269          12 :     return true;
    2270             : }
    2271             : 
    2272             : /************************************************************************/
    2273             : /*                         InitRaster()                                 */
    2274             : /************************************************************************/
    2275             : 
    2276         358 : bool GDALGeoPackageDataset::InitRaster(
    2277             :     GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
    2278             :     double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
    2279             :     const char *pszContentsMinY, const char *pszContentsMaxX,
    2280             :     const char *pszContentsMaxY, char **papszOpenOptionsIn,
    2281             :     const SQLResult &oResult, int nIdxInResult)
    2282             : {
    2283         358 :     m_osRasterTable = pszTableName;
    2284         358 :     m_dfTMSMinX = dfMinX;
    2285         358 :     m_dfTMSMaxY = dfMaxY;
    2286             : 
    2287             :     // Despite prior checking, the type might be Binary and
    2288             :     // SQLResultGetValue() not working properly on it
    2289         358 :     int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
    2290         358 :     if (nZoomLevel < 0 || nZoomLevel > 65536)
    2291             :     {
    2292           0 :         return false;
    2293             :     }
    2294         358 :     double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
    2295         358 :     double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
    2296         358 :     if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
    2297             :     {
    2298           0 :         return false;
    2299             :     }
    2300         358 :     int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
    2301         358 :     int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
    2302         358 :     if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
    2303             :         nTileHeight > 65536)
    2304             :     {
    2305           0 :         return false;
    2306             :     }
    2307             :     int nTileMatrixWidth = static_cast<int>(
    2308         716 :         std::min(static_cast<GIntBig>(INT_MAX),
    2309         358 :                  CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
    2310             :     int nTileMatrixHeight = static_cast<int>(
    2311         716 :         std::min(static_cast<GIntBig>(INT_MAX),
    2312         358 :                  CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
    2313         358 :     if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
    2314             :     {
    2315           0 :         return false;
    2316             :     }
    2317             : 
    2318             :     /* Use content bounds in priority over tile_matrix_set bounds */
    2319         358 :     double dfGDALMinX = dfMinX;
    2320         358 :     double dfGDALMinY = dfMinY;
    2321         358 :     double dfGDALMaxX = dfMaxX;
    2322         358 :     double dfGDALMaxY = dfMaxY;
    2323             :     pszContentsMinX =
    2324         358 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
    2325             :     pszContentsMinY =
    2326         358 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
    2327             :     pszContentsMaxX =
    2328         358 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
    2329             :     pszContentsMaxY =
    2330         358 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
    2331         358 :     if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
    2332         358 :         pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
    2333             :     {
    2334         715 :         if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
    2335         357 :             CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
    2336             :         {
    2337         357 :             dfGDALMinX = CPLAtof(pszContentsMinX);
    2338         357 :             dfGDALMinY = CPLAtof(pszContentsMinY);
    2339         357 :             dfGDALMaxX = CPLAtof(pszContentsMaxX);
    2340         357 :             dfGDALMaxY = CPLAtof(pszContentsMaxY);
    2341             :         }
    2342             :         else
    2343             :         {
    2344           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    2345             :                      "Illegal min_x/min_y/max_x/max_y values for %s in open "
    2346             :                      "options and/or gpkg_contents. Using bounds of "
    2347             :                      "gpkg_tile_matrix_set instead",
    2348             :                      pszTableName);
    2349             :         }
    2350             :     }
    2351         358 :     if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
    2352             :     {
    2353           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2354             :                  "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
    2355           0 :         return false;
    2356             :     }
    2357             : 
    2358         358 :     int nBandCount = 0;
    2359             :     const char *pszBAND_COUNT =
    2360         358 :         CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
    2361         358 :     if (poParentDS)
    2362             :     {
    2363          86 :         nBandCount = poParentDS->GetRasterCount();
    2364             :     }
    2365         272 :     else if (m_eDT != GDT_Byte)
    2366             :     {
    2367          65 :         if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
    2368           0 :             !EQUAL(pszBAND_COUNT, "1"))
    2369             :         {
    2370           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    2371             :                      "BAND_COUNT ignored for non-Byte data");
    2372             :         }
    2373          65 :         nBandCount = 1;
    2374             :     }
    2375             :     else
    2376             :     {
    2377         207 :         if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
    2378             :         {
    2379          69 :             nBandCount = atoi(pszBAND_COUNT);
    2380          69 :             if (nBandCount == 1)
    2381           5 :                 GetMetadata("IMAGE_STRUCTURE");
    2382             :         }
    2383             :         else
    2384             :         {
    2385         138 :             GetMetadata("IMAGE_STRUCTURE");
    2386         138 :             nBandCount = m_nBandCountFromMetadata;
    2387         138 :             if (nBandCount == 1)
    2388          38 :                 m_eTF = GPKG_TF_PNG;
    2389             :         }
    2390         207 :         if (nBandCount == 1 && !m_osTFFromMetadata.empty())
    2391             :         {
    2392           2 :             m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
    2393             :         }
    2394         207 :         if (nBandCount <= 0 || nBandCount > 4)
    2395          86 :             nBandCount = 4;
    2396             :     }
    2397             : 
    2398         358 :     return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
    2399             :                       dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
    2400             :                       nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
    2401         358 :                       dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
    2402             : }
    2403             : 
    2404             : /************************************************************************/
    2405             : /*                      ComputeTileAndPixelShifts()                     */
    2406             : /************************************************************************/
    2407             : 
    2408         781 : bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
    2409             : {
    2410             :     int nTileWidth, nTileHeight;
    2411         781 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    2412             : 
    2413             :     // Compute shift between GDAL origin and TileMatrixSet origin
    2414             :     const double dfShiftXPixels =
    2415         781 :         (m_adfGeoTransform[0] - m_dfTMSMinX) / m_adfGeoTransform[1];
    2416         781 :     if (dfShiftXPixels / nTileWidth <= INT_MIN ||
    2417         779 :         dfShiftXPixels / nTileWidth > INT_MAX)
    2418           2 :         return false;
    2419         779 :     const int64_t nShiftXPixels =
    2420         779 :         static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
    2421         779 :     m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
    2422         779 :     if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
    2423          11 :         m_nShiftXTiles--;
    2424         779 :     m_nShiftXPixelsMod =
    2425         779 :         (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
    2426             :         nTileWidth;
    2427             : 
    2428             :     const double dfShiftYPixels =
    2429         779 :         (m_adfGeoTransform[3] - m_dfTMSMaxY) / m_adfGeoTransform[5];
    2430         779 :     if (dfShiftYPixels / nTileHeight <= INT_MIN ||
    2431         779 :         dfShiftYPixels / nTileHeight > INT_MAX)
    2432           1 :         return false;
    2433         778 :     const int64_t nShiftYPixels =
    2434         778 :         static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
    2435         778 :     m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
    2436         778 :     if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
    2437          11 :         m_nShiftYTiles--;
    2438         778 :     m_nShiftYPixelsMod =
    2439         778 :         (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
    2440             :         nTileHeight;
    2441         778 :     return true;
    2442             : }
    2443             : 
    2444             : /************************************************************************/
    2445             : /*                            AllocCachedTiles()                        */
    2446             : /************************************************************************/
    2447             : 
    2448         778 : bool GDALGeoPackageDataset::AllocCachedTiles()
    2449             : {
    2450             :     int nTileWidth, nTileHeight;
    2451         778 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    2452             : 
    2453             :     // We currently need 4 caches because of
    2454             :     // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
    2455         778 :     const int nCacheCount = 4;
    2456             :     /*
    2457             :             (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
    2458             :             (GetUpdate() && m_eDT == GDT_Byte) ? 2 : 1;
    2459             :     */
    2460         778 :     m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
    2461             :         cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_Byte ? 4 : 1) *
    2462             :                           m_nDTSize),
    2463             :         nTileWidth, nTileHeight));
    2464         778 :     if (m_pabyCachedTiles == nullptr)
    2465             :     {
    2466           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
    2467             :                  nTileWidth, nTileHeight);
    2468           0 :         return false;
    2469             :     }
    2470             : 
    2471         778 :     return true;
    2472             : }
    2473             : 
    2474             : /************************************************************************/
    2475             : /*                         InitRaster()                                 */
    2476             : /************************************************************************/
    2477             : 
    2478         597 : bool GDALGeoPackageDataset::InitRaster(
    2479             :     GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
    2480             :     int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
    2481             :     double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
    2482             :     int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
    2483             :     double dfGDALMaxX, double dfGDALMaxY)
    2484             : {
    2485         597 :     m_osRasterTable = pszTableName;
    2486         597 :     m_dfTMSMinX = dfTMSMinX;
    2487         597 :     m_dfTMSMaxY = dfTMSMaxY;
    2488         597 :     m_nZoomLevel = nZoomLevel;
    2489         597 :     m_nTileMatrixWidth = nTileMatrixWidth;
    2490         597 :     m_nTileMatrixHeight = nTileMatrixHeight;
    2491             : 
    2492         597 :     m_bGeoTransformValid = true;
    2493         597 :     m_adfGeoTransform[0] = dfGDALMinX;
    2494         597 :     m_adfGeoTransform[1] = dfPixelXSize;
    2495         597 :     m_adfGeoTransform[3] = dfGDALMaxY;
    2496         597 :     m_adfGeoTransform[5] = -dfPixelYSize;
    2497         597 :     double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
    2498         597 :     double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
    2499         597 :     if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
    2500             :     {
    2501           0 :         CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
    2502             :                  dfRasterXSize, dfRasterYSize);
    2503           0 :         return false;
    2504             :     }
    2505         597 :     nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
    2506         597 :     nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
    2507             : 
    2508         597 :     if (poParentDS)
    2509             :     {
    2510         325 :         m_poParentDS = poParentDS;
    2511         325 :         eAccess = poParentDS->eAccess;
    2512         325 :         hDB = poParentDS->hDB;
    2513         325 :         m_eTF = poParentDS->m_eTF;
    2514         325 :         m_eDT = poParentDS->m_eDT;
    2515         325 :         m_nDTSize = poParentDS->m_nDTSize;
    2516         325 :         m_dfScale = poParentDS->m_dfScale;
    2517         325 :         m_dfOffset = poParentDS->m_dfOffset;
    2518         325 :         m_dfPrecision = poParentDS->m_dfPrecision;
    2519         325 :         m_usGPKGNull = poParentDS->m_usGPKGNull;
    2520         325 :         m_nQuality = poParentDS->m_nQuality;
    2521         325 :         m_nZLevel = poParentDS->m_nZLevel;
    2522         325 :         m_bDither = poParentDS->m_bDither;
    2523             :         /*m_nSRID = poParentDS->m_nSRID;*/
    2524         325 :         m_osWHERE = poParentDS->m_osWHERE;
    2525         325 :         SetDescription(CPLSPrintf("%s - zoom_level=%d",
    2526         325 :                                   poParentDS->GetDescription(), m_nZoomLevel));
    2527             :     }
    2528             : 
    2529        2094 :     for (int i = 1; i <= nBandCount; i++)
    2530             :     {
    2531             :         auto poNewBand = std::make_unique<GDALGeoPackageRasterBand>(
    2532        1497 :             this, nTileWidth, nTileHeight);
    2533        1497 :         if (poParentDS)
    2534             :         {
    2535         761 :             int bHasNoData = FALSE;
    2536             :             double dfNoDataValue =
    2537         761 :                 poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    2538         761 :             if (bHasNoData)
    2539          24 :                 poNewBand->SetNoDataValueInternal(dfNoDataValue);
    2540             :         }
    2541             : 
    2542        1497 :         if (nBandCount == 1 && m_poCTFromMetadata)
    2543             :         {
    2544           3 :             poNewBand->AssignColorTable(m_poCTFromMetadata.get());
    2545             :         }
    2546        1497 :         if (!m_osNodataValueFromMetadata.empty())
    2547             :         {
    2548           8 :             poNewBand->SetNoDataValueInternal(
    2549             :                 CPLAtof(m_osNodataValueFromMetadata.c_str()));
    2550             :         }
    2551             : 
    2552        1497 :         SetBand(i, std::move(poNewBand));
    2553             :     }
    2554             : 
    2555         597 :     if (!ComputeTileAndPixelShifts())
    2556             :     {
    2557           3 :         CPLError(CE_Failure, CPLE_AppDefined,
    2558             :                  "Overflow occurred in ComputeTileAndPixelShifts()");
    2559           3 :         return false;
    2560             :     }
    2561             : 
    2562         594 :     GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    2563         594 :     GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
    2564             :                                     CPLSPrintf("%d", m_nZoomLevel));
    2565             : 
    2566         594 :     return AllocCachedTiles();
    2567             : }
    2568             : 
    2569             : /************************************************************************/
    2570             : /*                 GDALGPKGMBTilesGetTileFormat()                       */
    2571             : /************************************************************************/
    2572             : 
    2573          80 : GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
    2574             : {
    2575          80 :     GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
    2576          80 :     if (pszTF)
    2577             :     {
    2578          80 :         if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
    2579           1 :             eTF = GPKG_TF_PNG_JPEG;
    2580          79 :         else if (EQUAL(pszTF, "PNG"))
    2581          46 :             eTF = GPKG_TF_PNG;
    2582          33 :         else if (EQUAL(pszTF, "PNG8"))
    2583           6 :             eTF = GPKG_TF_PNG8;
    2584          27 :         else if (EQUAL(pszTF, "JPEG"))
    2585          14 :             eTF = GPKG_TF_JPEG;
    2586          13 :         else if (EQUAL(pszTF, "WEBP"))
    2587          13 :             eTF = GPKG_TF_WEBP;
    2588             :         else
    2589             :         {
    2590           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    2591             :                      "Unsuppoted value for TILE_FORMAT: %s", pszTF);
    2592             :         }
    2593             :     }
    2594          80 :     return eTF;
    2595             : }
    2596             : 
    2597          28 : const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
    2598             : {
    2599          28 :     switch (eTF)
    2600             :     {
    2601          26 :         case GPKG_TF_PNG:
    2602             :         case GPKG_TF_PNG8:
    2603          26 :             return "png";
    2604           1 :         case GPKG_TF_JPEG:
    2605           1 :             return "jpg";
    2606           1 :         case GPKG_TF_WEBP:
    2607           1 :             return "webp";
    2608           0 :         default:
    2609           0 :             break;
    2610             :     }
    2611           0 :     CPLError(CE_Failure, CPLE_NotSupported,
    2612             :              "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
    2613           0 :     return nullptr;
    2614             : }
    2615             : 
    2616             : /************************************************************************/
    2617             : /*                         OpenRaster()                                 */
    2618             : /************************************************************************/
    2619             : 
    2620         274 : bool GDALGeoPackageDataset::OpenRaster(
    2621             :     const char *pszTableName, const char *pszIdentifier,
    2622             :     const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
    2623             :     double dfMaxX, double dfMaxY, const char *pszContentsMinX,
    2624             :     const char *pszContentsMinY, const char *pszContentsMaxX,
    2625             :     const char *pszContentsMaxY, bool bIsTiles, char **papszOpenOptionsIn)
    2626             : {
    2627         274 :     if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
    2628           0 :         return false;
    2629             : 
    2630             :     // Config option just for debug, and for example force set to NaN
    2631             :     // which is not supported
    2632         548 :     CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
    2633         548 :     CPLString osUom;
    2634         548 :     CPLString osFieldName;
    2635         548 :     CPLString osGridCellEncoding;
    2636         274 :     if (!bIsTiles)
    2637             :     {
    2638          65 :         char *pszSQL = sqlite3_mprintf(
    2639             :             "SELECT datatype, scale, offset, data_null, precision FROM "
    2640             :             "gpkg_2d_gridded_coverage_ancillary "
    2641             :             "WHERE tile_matrix_set_name = '%q' "
    2642             :             "AND datatype IN ('integer', 'float')"
    2643             :             "AND (scale > 0 OR scale IS NULL)",
    2644             :             pszTableName);
    2645          65 :         auto oResult = SQLQuery(hDB, pszSQL);
    2646          65 :         sqlite3_free(pszSQL);
    2647          65 :         if (!oResult || oResult->RowCount() == 0)
    2648             :         {
    2649           0 :             return false;
    2650             :         }
    2651          65 :         const char *pszDataType = oResult->GetValue(0, 0);
    2652          65 :         const char *pszScale = oResult->GetValue(1, 0);
    2653          65 :         const char *pszOffset = oResult->GetValue(2, 0);
    2654          65 :         const char *pszDataNull = oResult->GetValue(3, 0);
    2655          65 :         const char *pszPrecision = oResult->GetValue(4, 0);
    2656          65 :         if (pszDataNull)
    2657          23 :             osDataNull = pszDataNull;
    2658          65 :         if (EQUAL(pszDataType, "float"))
    2659             :         {
    2660           6 :             SetDataType(GDT_Float32);
    2661           6 :             m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
    2662             :         }
    2663             :         else
    2664             :         {
    2665          59 :             SetDataType(GDT_Float32);
    2666          59 :             m_eTF = GPKG_TF_PNG_16BIT;
    2667          59 :             const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
    2668          59 :             const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
    2669          59 :             if (dfScale == 1.0)
    2670             :             {
    2671          59 :                 if (dfOffset == 0.0)
    2672             :                 {
    2673          24 :                     SetDataType(GDT_UInt16);
    2674             :                 }
    2675          35 :                 else if (dfOffset == -32768.0)
    2676             :                 {
    2677          35 :                     SetDataType(GDT_Int16);
    2678             :                 }
    2679             :                 // coverity[tainted_data]
    2680           0 :                 else if (dfOffset == -32767.0 && !osDataNull.empty() &&
    2681           0 :                          CPLAtof(osDataNull) == 65535.0)
    2682             :                 // Given that we will map the nodata value to -32768
    2683             :                 {
    2684           0 :                     SetDataType(GDT_Int16);
    2685             :                 }
    2686             :             }
    2687             : 
    2688             :             // Check that the tile offset and scales are compatible of a
    2689             :             // final integer result.
    2690          59 :             if (m_eDT != GDT_Float32)
    2691             :             {
    2692             :                 // coverity[tainted_data]
    2693          59 :                 if (dfScale == 1.0 && dfOffset == -32768.0 &&
    2694         118 :                     !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
    2695             :                 {
    2696             :                     // Given that we will map the nodata value to -32768
    2697           9 :                     pszSQL = sqlite3_mprintf(
    2698             :                         "SELECT 1 FROM "
    2699             :                         "gpkg_2d_gridded_tile_ancillary WHERE "
    2700             :                         "tpudt_name = '%q' "
    2701             :                         "AND NOT ((offset = 0.0 or offset = 1.0) "
    2702             :                         "AND scale = 1.0) "
    2703             :                         "LIMIT 1",
    2704             :                         pszTableName);
    2705             :                 }
    2706             :                 else
    2707             :                 {
    2708          50 :                     pszSQL = sqlite3_mprintf(
    2709             :                         "SELECT 1 FROM "
    2710             :                         "gpkg_2d_gridded_tile_ancillary WHERE "
    2711             :                         "tpudt_name = '%q' "
    2712             :                         "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
    2713             :                         pszTableName);
    2714             :                 }
    2715          59 :                 sqlite3_stmt *hSQLStmt = nullptr;
    2716             :                 int rc =
    2717          59 :                     SQLPrepareWithError(hDB, pszSQL, -1, &hSQLStmt, nullptr);
    2718             : 
    2719          59 :                 if (rc == SQLITE_OK)
    2720             :                 {
    2721          59 :                     if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
    2722             :                     {
    2723           8 :                         SetDataType(GDT_Float32);
    2724             :                     }
    2725          59 :                     sqlite3_finalize(hSQLStmt);
    2726             :                 }
    2727          59 :                 sqlite3_free(pszSQL);
    2728             :             }
    2729             : 
    2730          59 :             SetGlobalOffsetScale(dfOffset, dfScale);
    2731             :         }
    2732          65 :         if (pszPrecision)
    2733          65 :             m_dfPrecision = CPLAtof(pszPrecision);
    2734             : 
    2735             :         // Request those columns in a separate query, so as to keep
    2736             :         // compatibility with pre OGC 17-066r1 databases
    2737             :         pszSQL =
    2738          65 :             sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
    2739             :                             "gpkg_2d_gridded_coverage_ancillary "
    2740             :                             "WHERE tile_matrix_set_name = '%q'",
    2741             :                             pszTableName);
    2742          65 :         CPLPushErrorHandler(CPLQuietErrorHandler);
    2743          65 :         oResult = SQLQuery(hDB, pszSQL);
    2744          65 :         CPLPopErrorHandler();
    2745          65 :         sqlite3_free(pszSQL);
    2746          65 :         if (oResult && oResult->RowCount() == 1)
    2747             :         {
    2748          64 :             const char *pszUom = oResult->GetValue(0, 0);
    2749          64 :             if (pszUom)
    2750           2 :                 osUom = pszUom;
    2751          64 :             const char *pszFieldName = oResult->GetValue(1, 0);
    2752          64 :             if (pszFieldName)
    2753          64 :                 osFieldName = pszFieldName;
    2754          64 :             const char *pszGridCellEncoding = oResult->GetValue(2, 0);
    2755          64 :             if (pszGridCellEncoding)
    2756          64 :                 osGridCellEncoding = pszGridCellEncoding;
    2757             :         }
    2758             :     }
    2759             : 
    2760         274 :     m_bRecordInsertedInGPKGContent = true;
    2761         274 :     m_nSRID = nSRSId;
    2762             : 
    2763         547 :     if (auto poSRS = GetSpatialRef(nSRSId))
    2764             :     {
    2765         273 :         m_oSRS = *(poSRS.get());
    2766             :     }
    2767             : 
    2768             :     /* Various sanity checks added in the SELECT */
    2769         274 :     char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
    2770         548 :     CPLString osQuotedTableName(pszQuotedTableName);
    2771         274 :     sqlite3_free(pszQuotedTableName);
    2772         274 :     char *pszSQL = sqlite3_mprintf(
    2773             :         "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
    2774             :         "tile_height, matrix_width, matrix_height "
    2775             :         "FROM gpkg_tile_matrix tm "
    2776             :         "WHERE table_name = %s "
    2777             :         // INT_MAX would be the theoretical maximum value to avoid
    2778             :         // overflows, but that's already a insane value.
    2779             :         "AND zoom_level >= 0 AND zoom_level <= 65536 "
    2780             :         "AND pixel_x_size > 0 AND pixel_y_size > 0 "
    2781             :         "AND tile_width >= 1 AND tile_width <= 65536 "
    2782             :         "AND tile_height >= 1 AND tile_height <= 65536 "
    2783             :         "AND matrix_width >= 1 AND matrix_height >= 1",
    2784             :         osQuotedTableName.c_str());
    2785         548 :     CPLString osSQL(pszSQL);
    2786             :     const char *pszZoomLevel =
    2787         274 :         CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
    2788         274 :     if (pszZoomLevel)
    2789             :     {
    2790           5 :         if (GetUpdate())
    2791           1 :             osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
    2792             :         else
    2793             :         {
    2794             :             osSQL += CPLSPrintf(
    2795             :                 " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
    2796             :                 "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
    2797             :                 atoi(pszZoomLevel), atoi(pszZoomLevel),
    2798           4 :                 osQuotedTableName.c_str());
    2799             :         }
    2800             :     }
    2801             :     // In read-only mode, only lists non empty zoom levels
    2802         269 :     else if (!GetUpdate())
    2803             :     {
    2804             :         osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
    2805             :                             "tm.zoom_level LIMIT 1)",
    2806         215 :                             osQuotedTableName.c_str());
    2807             :     }
    2808             :     else  // if( pszZoomLevel == nullptr )
    2809             :     {
    2810             :         osSQL +=
    2811             :             CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
    2812          54 :                        osQuotedTableName.c_str());
    2813             :     }
    2814         274 :     osSQL += " ORDER BY zoom_level DESC";
    2815             :     // To avoid denial of service.
    2816         274 :     osSQL += " LIMIT 100";
    2817             : 
    2818         548 :     auto oResult = SQLQuery(hDB, osSQL.c_str());
    2819         274 :     if (!oResult || oResult->RowCount() == 0)
    2820             :     {
    2821         114 :         if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
    2822         114 :             pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
    2823             :             pszContentsMaxY != nullptr)
    2824             :         {
    2825          56 :             osSQL = pszSQL;
    2826          56 :             osSQL += " ORDER BY zoom_level DESC";
    2827          56 :             if (!GetUpdate())
    2828          30 :                 osSQL += " LIMIT 1";
    2829          56 :             oResult = SQLQuery(hDB, osSQL.c_str());
    2830             :         }
    2831          57 :         if (!oResult || oResult->RowCount() == 0)
    2832             :         {
    2833           1 :             if (oResult && pszZoomLevel != nullptr)
    2834             :             {
    2835           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2836             :                          "ZOOM_LEVEL is probably not valid w.r.t tile "
    2837             :                          "table content");
    2838             :             }
    2839           1 :             sqlite3_free(pszSQL);
    2840           1 :             return false;
    2841             :         }
    2842             :     }
    2843         273 :     sqlite3_free(pszSQL);
    2844             : 
    2845             :     // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
    2846             :     // actually exist.
    2847             : 
    2848             :     // CAUTION: Do not move those variables inside inner scope !
    2849         546 :     CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
    2850             : 
    2851         273 :     if (CPLTestBool(
    2852             :             CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
    2853             :     {
    2854          13 :         pszSQL = sqlite3_mprintf(
    2855             :             "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
    2856             :             "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
    2857             :             pszTableName, atoi(oResult->GetValue(0, 0)));
    2858          13 :         auto oResult2 = SQLQuery(hDB, pszSQL);
    2859          13 :         sqlite3_free(pszSQL);
    2860          26 :         if (!oResult2 || oResult2->RowCount() == 0 ||
    2861             :             // Can happen if table is empty
    2862          38 :             oResult2->GetValue(0, 0) == nullptr ||
    2863             :             // Can happen if table has no NOT NULL constraint on tile_row
    2864             :             // and that all tile_row are NULL
    2865          12 :             oResult2->GetValue(1, 0) == nullptr)
    2866             :         {
    2867           1 :             return false;
    2868             :         }
    2869          12 :         const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
    2870          12 :         const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
    2871          12 :         const int nTileWidth = atoi(oResult->GetValue(3, 0));
    2872          12 :         const int nTileHeight = atoi(oResult->GetValue(4, 0));
    2873             :         osContentsMinX =
    2874          24 :             CPLSPrintf("%.17g", dfMinX + dfPixelXSize * nTileWidth *
    2875          12 :                                              atoi(oResult2->GetValue(0, 0)));
    2876             :         osContentsMaxY =
    2877          24 :             CPLSPrintf("%.17g", dfMaxY - dfPixelYSize * nTileHeight *
    2878          12 :                                              atoi(oResult2->GetValue(1, 0)));
    2879             :         osContentsMaxX = CPLSPrintf(
    2880          24 :             "%.17g", dfMinX + dfPixelXSize * nTileWidth *
    2881          12 :                                   (1 + atoi(oResult2->GetValue(2, 0))));
    2882             :         osContentsMinY = CPLSPrintf(
    2883          24 :             "%.17g", dfMaxY - dfPixelYSize * nTileHeight *
    2884          12 :                                   (1 + atoi(oResult2->GetValue(3, 0))));
    2885          12 :         pszContentsMinX = osContentsMinX.c_str();
    2886          12 :         pszContentsMinY = osContentsMinY.c_str();
    2887          12 :         pszContentsMaxX = osContentsMaxX.c_str();
    2888          12 :         pszContentsMaxY = osContentsMaxY.c_str();
    2889             :     }
    2890             : 
    2891         272 :     if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
    2892             :                     pszContentsMinX, pszContentsMinY, pszContentsMaxX,
    2893         272 :                     pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
    2894             :     {
    2895           3 :         return false;
    2896             :     }
    2897             : 
    2898             :     auto poBand =
    2899         269 :         reinterpret_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
    2900         269 :     if (!osDataNull.empty())
    2901             :     {
    2902          23 :         double dfGPKGNoDataValue = CPLAtof(osDataNull);
    2903          23 :         if (m_eTF == GPKG_TF_PNG_16BIT)
    2904             :         {
    2905          21 :             if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
    2906          21 :                 static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
    2907             :             {
    2908           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2909             :                          "data_null = %.17g is invalid for integer data_type",
    2910             :                          dfGPKGNoDataValue);
    2911             :             }
    2912             :             else
    2913             :             {
    2914          21 :                 m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
    2915          21 :                 if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
    2916           9 :                     dfGPKGNoDataValue = -32768.0;
    2917          12 :                 else if (m_eDT == GDT_Float32)
    2918             :                 {
    2919             :                     // Pick a value that is unlikely to be hit with offset &
    2920             :                     // scale
    2921           4 :                     dfGPKGNoDataValue = -std::numeric_limits<float>::max();
    2922             :                 }
    2923          21 :                 poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
    2924             :             }
    2925             :         }
    2926             :         else
    2927             :         {
    2928           2 :             poBand->SetNoDataValueInternal(
    2929           2 :                 static_cast<float>(dfGPKGNoDataValue));
    2930             :         }
    2931             :     }
    2932         269 :     if (!osUom.empty())
    2933             :     {
    2934           2 :         poBand->SetUnitTypeInternal(osUom);
    2935             :     }
    2936         269 :     if (!osFieldName.empty())
    2937             :     {
    2938          64 :         GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
    2939             :     }
    2940         269 :     if (!osGridCellEncoding.empty())
    2941             :     {
    2942          64 :         if (osGridCellEncoding == "grid-value-is-center")
    2943             :         {
    2944          15 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    2945             :                                             GDALMD_AOP_POINT);
    2946             :         }
    2947          49 :         else if (osGridCellEncoding == "grid-value-is-area")
    2948             :         {
    2949          45 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    2950             :                                             GDALMD_AOP_AREA);
    2951             :         }
    2952             :         else
    2953             :         {
    2954           4 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    2955             :                                             GDALMD_AOP_POINT);
    2956           4 :             GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
    2957             :                 "GRID_CELL_ENCODING", osGridCellEncoding);
    2958             :         }
    2959             :     }
    2960             : 
    2961         269 :     CheckUnknownExtensions(true);
    2962             : 
    2963             :     // Do this after CheckUnknownExtensions() so that m_eTF is set to
    2964             :     // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
    2965         269 :     const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
    2966         269 :     if (pszTF)
    2967             :     {
    2968           4 :         if (!GetUpdate())
    2969             :         {
    2970           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    2971             :                      "TILE_FORMAT open option ignored in read-only mode");
    2972             :         }
    2973           4 :         else if (m_eTF == GPKG_TF_PNG_16BIT ||
    2974           4 :                  m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
    2975             :         {
    2976           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    2977             :                      "TILE_FORMAT open option ignored on gridded coverages");
    2978             :         }
    2979             :         else
    2980             :         {
    2981           4 :             GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
    2982           4 :             if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
    2983             :             {
    2984           1 :                 if (!RegisterWebPExtension())
    2985           0 :                     return false;
    2986             :             }
    2987           4 :             m_eTF = eTF;
    2988             :         }
    2989             :     }
    2990             : 
    2991         269 :     ParseCompressionOptions(papszOpenOptionsIn);
    2992             : 
    2993         269 :     m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
    2994             : 
    2995             :     // Set metadata
    2996         269 :     if (pszIdentifier && pszIdentifier[0])
    2997         269 :         GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
    2998         269 :     if (pszDescription && pszDescription[0])
    2999          21 :         GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
    3000             : 
    3001             :     // Add overviews
    3002         354 :     for (int i = 1; i < oResult->RowCount(); i++)
    3003             :     {
    3004          86 :         auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
    3005          86 :         poOvrDS->ShareLockWithParentDataset(this);
    3006         172 :         if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
    3007             :                                  dfMaxY, pszContentsMinX, pszContentsMinY,
    3008             :                                  pszContentsMaxX, pszContentsMaxY,
    3009          86 :                                  papszOpenOptionsIn, *oResult, i))
    3010             :         {
    3011           0 :             break;
    3012             :         }
    3013             : 
    3014             :         int nTileWidth, nTileHeight;
    3015          86 :         poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3016             :         const bool bStop =
    3017          87 :             (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
    3018           1 :              poOvrDS->GetRasterYSize() < nTileHeight);
    3019             : 
    3020          86 :         m_apoOverviewDS.push_back(std::move(poOvrDS));
    3021             : 
    3022          86 :         if (bStop)
    3023             :         {
    3024           1 :             break;
    3025             :         }
    3026             :     }
    3027             : 
    3028         269 :     return true;
    3029             : }
    3030             : 
    3031             : /************************************************************************/
    3032             : /*                           GetSpatialRef()                            */
    3033             : /************************************************************************/
    3034             : 
    3035          17 : const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
    3036             : {
    3037          17 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3038             : }
    3039             : 
    3040             : /************************************************************************/
    3041             : /*                           SetSpatialRef()                            */
    3042             : /************************************************************************/
    3043             : 
    3044         150 : CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    3045             : {
    3046         150 :     if (nBands == 0)
    3047             :     {
    3048           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3049             :                  "SetProjection() not supported on a dataset with 0 band");
    3050           1 :         return CE_Failure;
    3051             :     }
    3052         149 :     if (eAccess != GA_Update)
    3053             :     {
    3054           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3055             :                  "SetProjection() not supported on read-only dataset");
    3056           1 :         return CE_Failure;
    3057             :     }
    3058             : 
    3059         148 :     const int nSRID = GetSrsId(poSRS);
    3060         296 :     const auto poTS = GetTilingScheme(m_osTilingScheme);
    3061         148 :     if (poTS && nSRID != poTS->nEPSGCode)
    3062             :     {
    3063           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    3064             :                  "Projection should be EPSG:%d for %s tiling scheme",
    3065           1 :                  poTS->nEPSGCode, m_osTilingScheme.c_str());
    3066           1 :         return CE_Failure;
    3067             :     }
    3068             : 
    3069         147 :     m_nSRID = nSRID;
    3070         147 :     m_oSRS.Clear();
    3071         147 :     if (poSRS)
    3072         146 :         m_oSRS = *poSRS;
    3073             : 
    3074         147 :     if (m_bRecordInsertedInGPKGContent)
    3075             :     {
    3076         119 :         char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
    3077             :                                        "WHERE lower(table_name) = lower('%q')",
    3078             :                                        m_nSRID, m_osRasterTable.c_str());
    3079         119 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
    3080         119 :         sqlite3_free(pszSQL);
    3081         119 :         if (eErr != OGRERR_NONE)
    3082           0 :             return CE_Failure;
    3083             : 
    3084         119 :         pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
    3085             :                                  "WHERE lower(table_name) = lower('%q')",
    3086             :                                  m_nSRID, m_osRasterTable.c_str());
    3087         119 :         eErr = SQLCommand(hDB, pszSQL);
    3088         119 :         sqlite3_free(pszSQL);
    3089         119 :         if (eErr != OGRERR_NONE)
    3090           0 :             return CE_Failure;
    3091             :     }
    3092             : 
    3093         147 :     return CE_None;
    3094             : }
    3095             : 
    3096             : /************************************************************************/
    3097             : /*                          GetGeoTransform()                           */
    3098             : /************************************************************************/
    3099             : 
    3100          33 : CPLErr GDALGeoPackageDataset::GetGeoTransform(double *padfGeoTransform)
    3101             : {
    3102          33 :     memcpy(padfGeoTransform, m_adfGeoTransform.data(), 6 * sizeof(double));
    3103          33 :     if (!m_bGeoTransformValid)
    3104           2 :         return CE_Failure;
    3105             :     else
    3106          31 :         return CE_None;
    3107             : }
    3108             : 
    3109             : /************************************************************************/
    3110             : /*                          SetGeoTransform()                           */
    3111             : /************************************************************************/
    3112             : 
    3113         189 : CPLErr GDALGeoPackageDataset::SetGeoTransform(double *padfGeoTransform)
    3114             : {
    3115         189 :     if (nBands == 0)
    3116             :     {
    3117           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    3118             :                  "SetGeoTransform() not supported on a dataset with 0 band");
    3119           2 :         return CE_Failure;
    3120             :     }
    3121         187 :     if (eAccess != GA_Update)
    3122             :     {
    3123           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3124             :                  "SetGeoTransform() not supported on read-only dataset");
    3125           1 :         return CE_Failure;
    3126             :     }
    3127         186 :     if (m_bGeoTransformValid)
    3128             :     {
    3129           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3130             :                  "Cannot modify geotransform once set");
    3131           1 :         return CE_Failure;
    3132             :     }
    3133         185 :     if (padfGeoTransform[2] != 0.0 || padfGeoTransform[4] != 0 ||
    3134         185 :         padfGeoTransform[5] > 0.0)
    3135             :     {
    3136           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3137             :                  "Only north-up non rotated geotransform supported");
    3138           0 :         return CE_Failure;
    3139             :     }
    3140             : 
    3141         185 :     if (m_nZoomLevel < 0)
    3142             :     {
    3143         184 :         const auto poTS = GetTilingScheme(m_osTilingScheme);
    3144         184 :         if (poTS)
    3145             :         {
    3146          20 :             double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
    3147          20 :             double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
    3148         199 :             for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
    3149         179 :                  m_nZoomLevel++)
    3150             :             {
    3151         198 :                 double dfExpectedPixelXSize =
    3152         198 :                     dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
    3153         198 :                 double dfExpectedPixelYSize =
    3154         198 :                     dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
    3155         198 :                 if (fabs(padfGeoTransform[1] - dfExpectedPixelXSize) <
    3156         198 :                         1e-8 * dfExpectedPixelXSize &&
    3157          19 :                     fabs(fabs(padfGeoTransform[5]) - dfExpectedPixelYSize) <
    3158          19 :                         1e-8 * dfExpectedPixelYSize)
    3159             :                 {
    3160          19 :                     break;
    3161             :                 }
    3162             :             }
    3163          20 :             if (m_nZoomLevel == MAX_ZOOM_LEVEL)
    3164             :             {
    3165           1 :                 m_nZoomLevel = -1;
    3166           1 :                 CPLError(
    3167             :                     CE_Failure, CPLE_NotSupported,
    3168             :                     "Could not find an appropriate zoom level of %s tiling "
    3169             :                     "scheme that matches raster pixel size",
    3170             :                     m_osTilingScheme.c_str());
    3171           1 :                 return CE_Failure;
    3172             :             }
    3173             :         }
    3174             :     }
    3175             : 
    3176         184 :     memcpy(m_adfGeoTransform.data(), padfGeoTransform, 6 * sizeof(double));
    3177         184 :     m_bGeoTransformValid = true;
    3178             : 
    3179         184 :     return FinalizeRasterRegistration();
    3180             : }
    3181             : 
    3182             : /************************************************************************/
    3183             : /*                      FinalizeRasterRegistration()                    */
    3184             : /************************************************************************/
    3185             : 
    3186         184 : CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
    3187             : {
    3188             :     OGRErr eErr;
    3189             : 
    3190         184 :     m_dfTMSMinX = m_adfGeoTransform[0];
    3191         184 :     m_dfTMSMaxY = m_adfGeoTransform[3];
    3192             : 
    3193             :     int nTileWidth, nTileHeight;
    3194         184 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3195             : 
    3196         184 :     if (m_nZoomLevel < 0)
    3197             :     {
    3198         164 :         m_nZoomLevel = 0;
    3199         238 :         while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
    3200         164 :                (nRasterYSize >> m_nZoomLevel) > nTileHeight)
    3201          74 :             m_nZoomLevel++;
    3202             :     }
    3203             : 
    3204         184 :     double dfPixelXSizeZoomLevel0 = m_adfGeoTransform[1] * (1 << m_nZoomLevel);
    3205             :     double dfPixelYSizeZoomLevel0 =
    3206         184 :         fabs(m_adfGeoTransform[5]) * (1 << m_nZoomLevel);
    3207             :     int nTileXCountZoomLevel0 =
    3208         184 :         std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
    3209             :     int nTileYCountZoomLevel0 =
    3210         184 :         std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
    3211             : 
    3212         368 :     const auto poTS = GetTilingScheme(m_osTilingScheme);
    3213         184 :     if (poTS)
    3214             :     {
    3215          20 :         CPLAssert(m_nZoomLevel >= 0);
    3216          20 :         m_dfTMSMinX = poTS->dfMinX;
    3217          20 :         m_dfTMSMaxY = poTS->dfMaxY;
    3218          20 :         dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
    3219          20 :         dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
    3220          20 :         nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
    3221          20 :         nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
    3222             :     }
    3223         184 :     m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
    3224         184 :     m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
    3225             : 
    3226         184 :     if (!ComputeTileAndPixelShifts())
    3227             :     {
    3228           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3229             :                  "Overflow occurred in ComputeTileAndPixelShifts()");
    3230           0 :         return CE_Failure;
    3231             :     }
    3232             : 
    3233         184 :     if (!AllocCachedTiles())
    3234             :     {
    3235           0 :         return CE_Failure;
    3236             :     }
    3237             : 
    3238         184 :     double dfGDALMinX = m_adfGeoTransform[0];
    3239             :     double dfGDALMinY =
    3240         184 :         m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
    3241             :     double dfGDALMaxX =
    3242         184 :         m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
    3243         184 :     double dfGDALMaxY = m_adfGeoTransform[3];
    3244             : 
    3245         184 :     if (SoftStartTransaction() != OGRERR_NONE)
    3246           0 :         return CE_Failure;
    3247             : 
    3248             :     const char *pszCurrentDate =
    3249         184 :         CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
    3250             :     CPLString osInsertGpkgContentsFormatting(
    3251             :         "INSERT INTO gpkg_contents "
    3252             :         "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
    3253             :         "last_change,srs_id) VALUES "
    3254         368 :         "('%q','%q','%q','%q',%.17g,%.17g,%.17g,%.17g,");
    3255         184 :     osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
    3256         184 :     osInsertGpkgContentsFormatting += ",%d)";
    3257         368 :     char *pszSQL = sqlite3_mprintf(
    3258             :         osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
    3259         184 :         (m_eDT == GDT_Byte) ? "tiles" : "2d-gridded-coverage",
    3260             :         m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
    3261             :         dfGDALMaxX, dfGDALMaxY,
    3262             :         pszCurrentDate ? pszCurrentDate
    3263             :                        : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
    3264             :         m_nSRID);
    3265             : 
    3266         184 :     eErr = SQLCommand(hDB, pszSQL);
    3267         184 :     sqlite3_free(pszSQL);
    3268         184 :     if (eErr != OGRERR_NONE)
    3269             :     {
    3270           8 :         SoftRollbackTransaction();
    3271           8 :         return CE_Failure;
    3272             :     }
    3273             : 
    3274         176 :     double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
    3275             :                                          dfPixelXSizeZoomLevel0;
    3276         176 :     double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
    3277             :                                          dfPixelYSizeZoomLevel0;
    3278             : 
    3279             :     pszSQL =
    3280         176 :         sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
    3281             :                         "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
    3282             :                         "('%q',%d,%.17g,%.17g,%.17g,%.17g)",
    3283             :                         m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
    3284             :                         dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
    3285         176 :     eErr = SQLCommand(hDB, pszSQL);
    3286         176 :     sqlite3_free(pszSQL);
    3287         176 :     if (eErr != OGRERR_NONE)
    3288             :     {
    3289           0 :         SoftRollbackTransaction();
    3290           0 :         return CE_Failure;
    3291             :     }
    3292             : 
    3293         176 :     m_apoOverviewDS.resize(m_nZoomLevel);
    3294             : 
    3295         587 :     for (int i = 0; i <= m_nZoomLevel; i++)
    3296             :     {
    3297         411 :         double dfPixelXSizeZoomLevel = 0.0;
    3298         411 :         double dfPixelYSizeZoomLevel = 0.0;
    3299         411 :         int nTileMatrixWidth = 0;
    3300         411 :         int nTileMatrixHeight = 0;
    3301         411 :         if (EQUAL(m_osTilingScheme, "CUSTOM"))
    3302             :         {
    3303         230 :             dfPixelXSizeZoomLevel =
    3304         230 :                 m_adfGeoTransform[1] * (1 << (m_nZoomLevel - i));
    3305         230 :             dfPixelYSizeZoomLevel =
    3306         230 :                 fabs(m_adfGeoTransform[5]) * (1 << (m_nZoomLevel - i));
    3307             :         }
    3308             :         else
    3309             :         {
    3310         181 :             dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
    3311         181 :             dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
    3312             :         }
    3313         411 :         nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
    3314         411 :         nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
    3315             : 
    3316         411 :         pszSQL = sqlite3_mprintf(
    3317             :             "INSERT INTO gpkg_tile_matrix "
    3318             :             "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
    3319             :             "height,pixel_x_size,pixel_y_size) VALUES "
    3320             :             "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
    3321             :             m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
    3322             :             nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
    3323             :             dfPixelYSizeZoomLevel);
    3324         411 :         eErr = SQLCommand(hDB, pszSQL);
    3325         411 :         sqlite3_free(pszSQL);
    3326         411 :         if (eErr != OGRERR_NONE)
    3327             :         {
    3328           0 :             SoftRollbackTransaction();
    3329           0 :             return CE_Failure;
    3330             :         }
    3331             : 
    3332         411 :         if (i < m_nZoomLevel)
    3333             :         {
    3334         470 :             auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
    3335         235 :             poOvrDS->ShareLockWithParentDataset(this);
    3336         235 :             poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
    3337             :                                 m_dfTMSMaxY, dfPixelXSizeZoomLevel,
    3338             :                                 dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
    3339             :                                 nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
    3340             :                                 dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
    3341             : 
    3342         235 :             m_apoOverviewDS[m_nZoomLevel - 1 - i] = std::move(poOvrDS);
    3343             :         }
    3344             :     }
    3345             : 
    3346         176 :     if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
    3347             :     {
    3348          40 :         eErr = SQLCommand(
    3349             :             hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
    3350          40 :         m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
    3351          40 :         if (eErr != OGRERR_NONE)
    3352             :         {
    3353           0 :             SoftRollbackTransaction();
    3354           0 :             return CE_Failure;
    3355             :         }
    3356             :     }
    3357             : 
    3358         176 :     SoftCommitTransaction();
    3359             : 
    3360         176 :     m_apoOverviewDS.resize(m_nZoomLevel);
    3361         176 :     m_bRecordInsertedInGPKGContent = true;
    3362             : 
    3363         176 :     return CE_None;
    3364             : }
    3365             : 
    3366             : /************************************************************************/
    3367             : /*                             FlushCache()                             */
    3368             : /************************************************************************/
    3369             : 
    3370        2550 : CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
    3371             : {
    3372        2550 :     if (m_bInFlushCache)
    3373           0 :         return CE_None;
    3374             : 
    3375        2550 :     if (eAccess == GA_Update || !m_bMetadataDirty)
    3376             :     {
    3377        2547 :         SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    3378             :     }
    3379             : 
    3380        2550 :     if (m_bRemoveOGREmptyTable)
    3381             :     {
    3382         637 :         m_bRemoveOGREmptyTable = false;
    3383         637 :         RemoveOGREmptyTable();
    3384             :     }
    3385             : 
    3386        2550 :     CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
    3387             : 
    3388        2550 :     FlushMetadata();
    3389             : 
    3390        2550 :     if (eAccess == GA_Update || !m_bMetadataDirty)
    3391             :     {
    3392             :         // Needed again as above IFlushCacheWithErrCode()
    3393             :         // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
    3394             :         // which modifies metadata
    3395        2550 :         SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    3396             :     }
    3397             : 
    3398        2550 :     return eErr;
    3399             : }
    3400             : 
    3401        4784 : CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
    3402             : 
    3403             : {
    3404        4784 :     if (m_bInFlushCache)
    3405        2167 :         return CE_None;
    3406        2617 :     m_bInFlushCache = true;
    3407        2617 :     if (hDB && eAccess == GA_ReadOnly && bAtClosing)
    3408             :     {
    3409             :         // Clean-up metadata that will go to PAM by removing items that
    3410             :         // are reconstructed.
    3411        1924 :         CPLStringList aosMD;
    3412        1584 :         for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
    3413             :              ++papszIter)
    3414             :         {
    3415         622 :             char *pszKey = nullptr;
    3416         622 :             CPLParseNameValue(*papszIter, &pszKey);
    3417        1244 :             if (pszKey &&
    3418         622 :                 (EQUAL(pszKey, "AREA_OR_POINT") ||
    3419         477 :                  EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
    3420         256 :                  EQUAL(pszKey, "ZOOM_LEVEL") ||
    3421         652 :                  STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
    3422             :             {
    3423             :                 // remove it
    3424             :             }
    3425             :             else
    3426             :             {
    3427          30 :                 aosMD.AddString(*papszIter);
    3428             :             }
    3429         622 :             CPLFree(pszKey);
    3430             :         }
    3431         962 :         oMDMD.SetMetadata(aosMD.List());
    3432         962 :         oMDMD.SetMetadata(nullptr, "IMAGE_STRUCTURE");
    3433             : 
    3434        1924 :         GDALPamDataset::FlushCache(bAtClosing);
    3435             :     }
    3436             :     else
    3437             :     {
    3438             :         // Short circuit GDALPamDataset to avoid serialization to .aux.xml
    3439        1655 :         GDALDataset::FlushCache(bAtClosing);
    3440             :     }
    3441             : 
    3442        6482 :     for (auto &poLayer : m_apoLayers)
    3443             :     {
    3444        3865 :         poLayer->RunDeferredCreationIfNecessary();
    3445        3865 :         poLayer->CreateSpatialIndexIfNecessary();
    3446             :     }
    3447             : 
    3448             :     // Update raster table last_change column in gpkg_contents if needed
    3449        2617 :     if (m_bHasModifiedTiles)
    3450             :     {
    3451         536 :         for (int i = 1; i <= nBands; ++i)
    3452             :         {
    3453             :             auto poBand =
    3454         357 :                 cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
    3455         357 :             if (!poBand->HaveStatsMetadataBeenSetInThisSession())
    3456             :             {
    3457         344 :                 poBand->InvalidateStatistics();
    3458         344 :                 if (psPam && psPam->pszPamFilename)
    3459         344 :                     VSIUnlink(psPam->pszPamFilename);
    3460             :             }
    3461             :         }
    3462             : 
    3463         179 :         UpdateGpkgContentsLastChange(m_osRasterTable);
    3464             : 
    3465         179 :         m_bHasModifiedTiles = false;
    3466             :     }
    3467             : 
    3468        2617 :     CPLErr eErr = FlushTiles();
    3469             : 
    3470        2617 :     m_bInFlushCache = false;
    3471        2617 :     return eErr;
    3472             : }
    3473             : 
    3474             : /************************************************************************/
    3475             : /*                       GetCurrentDateEscapedSQL()                      */
    3476             : /************************************************************************/
    3477             : 
    3478        1863 : std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
    3479             : {
    3480             :     const char *pszCurrentDate =
    3481        1863 :         CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
    3482        1863 :     if (pszCurrentDate)
    3483          10 :         return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
    3484        1858 :     return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
    3485             : }
    3486             : 
    3487             : /************************************************************************/
    3488             : /*                    UpdateGpkgContentsLastChange()                    */
    3489             : /************************************************************************/
    3490             : 
    3491             : OGRErr
    3492         806 : GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
    3493             : {
    3494             :     char *pszSQL =
    3495         806 :         sqlite3_mprintf("UPDATE gpkg_contents SET "
    3496             :                         "last_change = %s "
    3497             :                         "WHERE lower(table_name) = lower('%q')",
    3498        1612 :                         GetCurrentDateEscapedSQL().c_str(), pszTableName);
    3499         806 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
    3500         806 :     sqlite3_free(pszSQL);
    3501         806 :     return eErr;
    3502             : }
    3503             : 
    3504             : /************************************************************************/
    3505             : /*                          IBuildOverviews()                           */
    3506             : /************************************************************************/
    3507             : 
    3508          20 : CPLErr GDALGeoPackageDataset::IBuildOverviews(
    3509             :     const char *pszResampling, int nOverviews, const int *panOverviewList,
    3510             :     int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
    3511             :     void *pProgressData, CSLConstList papszOptions)
    3512             : {
    3513          20 :     if (GetAccess() != GA_Update)
    3514             :     {
    3515           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3516             :                  "Overview building not supported on a database opened in "
    3517             :                  "read-only mode");
    3518           1 :         return CE_Failure;
    3519             :     }
    3520          19 :     if (m_poParentDS != nullptr)
    3521             :     {
    3522           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3523             :                  "Overview building not supported on overview dataset");
    3524           1 :         return CE_Failure;
    3525             :     }
    3526             : 
    3527          18 :     if (nOverviews == 0)
    3528             :     {
    3529           5 :         for (auto &poOvrDS : m_apoOverviewDS)
    3530           3 :             poOvrDS->FlushCache(false);
    3531             : 
    3532           2 :         SoftStartTransaction();
    3533             : 
    3534           2 :         if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
    3535             :         {
    3536           1 :             char *pszSQL = sqlite3_mprintf(
    3537             :                 "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
    3538             :                 "(SELECT y.id FROM \"%w\" x "
    3539             :                 "JOIN gpkg_2d_gridded_tile_ancillary y "
    3540             :                 "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
    3541             :                 "x.zoom_level < %d)",
    3542             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
    3543           1 :             OGRErr eErr = SQLCommand(hDB, pszSQL);
    3544           1 :             sqlite3_free(pszSQL);
    3545           1 :             if (eErr != OGRERR_NONE)
    3546             :             {
    3547           0 :                 SoftRollbackTransaction();
    3548           0 :                 return CE_Failure;
    3549             :             }
    3550             :         }
    3551             : 
    3552             :         char *pszSQL =
    3553           2 :             sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
    3554             :                             m_osRasterTable.c_str(), m_nZoomLevel);
    3555           2 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
    3556           2 :         sqlite3_free(pszSQL);
    3557           2 :         if (eErr != OGRERR_NONE)
    3558             :         {
    3559           0 :             SoftRollbackTransaction();
    3560           0 :             return CE_Failure;
    3561             :         }
    3562             : 
    3563           2 :         SoftCommitTransaction();
    3564             : 
    3565           2 :         return CE_None;
    3566             :     }
    3567             : 
    3568          16 :     if (nBandsIn != nBands)
    3569             :     {
    3570           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3571             :                  "Generation of overviews in GPKG only"
    3572             :                  "supported when operating on all bands.");
    3573           0 :         return CE_Failure;
    3574             :     }
    3575             : 
    3576          16 :     if (m_apoOverviewDS.empty())
    3577             :     {
    3578           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3579             :                  "Image too small to support overviews");
    3580           0 :         return CE_Failure;
    3581             :     }
    3582             : 
    3583          16 :     FlushCache(false);
    3584          60 :     for (int i = 0; i < nOverviews; i++)
    3585             :     {
    3586          47 :         if (panOverviewList[i] < 2)
    3587             :         {
    3588           1 :             CPLError(CE_Failure, CPLE_IllegalArg,
    3589             :                      "Overview factor must be >= 2");
    3590           1 :             return CE_Failure;
    3591             :         }
    3592             : 
    3593          46 :         bool bFound = false;
    3594          46 :         int jCandidate = -1;
    3595          46 :         int nMaxOvFactor = 0;
    3596         196 :         for (int j = 0; j < static_cast<int>(m_apoOverviewDS.size()); j++)
    3597             :         {
    3598         190 :             const auto poODS = m_apoOverviewDS[j].get();
    3599             :             const int nOvFactor = static_cast<int>(
    3600         190 :                 0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
    3601             : 
    3602         190 :             nMaxOvFactor = nOvFactor;
    3603             : 
    3604         190 :             if (nOvFactor == panOverviewList[i])
    3605             :             {
    3606          40 :                 bFound = true;
    3607          40 :                 break;
    3608             :             }
    3609             : 
    3610         150 :             if (jCandidate < 0 && nOvFactor > panOverviewList[i])
    3611           1 :                 jCandidate = j;
    3612             :         }
    3613             : 
    3614          46 :         if (!bFound)
    3615             :         {
    3616             :             /* Mostly for debug */
    3617           6 :             if (!CPLTestBool(CPLGetConfigOption(
    3618             :                     "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
    3619             :             {
    3620           2 :                 CPLString osOvrList;
    3621           4 :                 for (const auto &poODS : m_apoOverviewDS)
    3622             :                 {
    3623             :                     const int nOvFactor =
    3624           2 :                         static_cast<int>(0.5 + poODS->m_adfGeoTransform[1] /
    3625           2 :                                                    m_adfGeoTransform[1]);
    3626             : 
    3627           2 :                     if (!osOvrList.empty())
    3628           0 :                         osOvrList += ' ';
    3629           2 :                     osOvrList += CPLSPrintf("%d", nOvFactor);
    3630             :                 }
    3631           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    3632             :                          "Only overviews %s can be computed",
    3633             :                          osOvrList.c_str());
    3634           2 :                 return CE_Failure;
    3635             :             }
    3636             :             else
    3637             :             {
    3638           4 :                 int nOvFactor = panOverviewList[i];
    3639           4 :                 if (jCandidate < 0)
    3640           3 :                     jCandidate = static_cast<int>(m_apoOverviewDS.size());
    3641             : 
    3642           4 :                 int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
    3643           4 :                 int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
    3644           4 :                 if (!(jCandidate == static_cast<int>(m_apoOverviewDS.size()) &&
    3645           5 :                       nOvFactor == 2 * nMaxOvFactor) &&
    3646           1 :                     !m_bZoomOther)
    3647             :                 {
    3648           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    3649             :                              "Use of overview factor %d causes gpkg_zoom_other "
    3650             :                              "extension to be needed",
    3651             :                              nOvFactor);
    3652           1 :                     RegisterZoomOtherExtension();
    3653           1 :                     m_bZoomOther = true;
    3654             :                 }
    3655             : 
    3656           4 :                 SoftStartTransaction();
    3657             : 
    3658           4 :                 CPLAssert(jCandidate > 0);
    3659             :                 const int nNewZoomLevel =
    3660           4 :                     m_apoOverviewDS[jCandidate - 1]->m_nZoomLevel;
    3661             : 
    3662             :                 char *pszSQL;
    3663             :                 OGRErr eErr;
    3664          24 :                 for (int k = 0; k <= jCandidate; k++)
    3665             :                 {
    3666          60 :                     pszSQL = sqlite3_mprintf(
    3667             :                         "UPDATE gpkg_tile_matrix SET zoom_level = %d "
    3668             :                         "WHERE lower(table_name) = lower('%q') AND zoom_level "
    3669             :                         "= %d",
    3670          20 :                         m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
    3671          20 :                         m_nZoomLevel - k);
    3672          20 :                     eErr = SQLCommand(hDB, pszSQL);
    3673          20 :                     sqlite3_free(pszSQL);
    3674          20 :                     if (eErr != OGRERR_NONE)
    3675             :                     {
    3676           0 :                         SoftRollbackTransaction();
    3677           0 :                         return CE_Failure;
    3678             :                     }
    3679             : 
    3680             :                     pszSQL =
    3681          20 :                         sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
    3682             :                                         "WHERE zoom_level = %d",
    3683             :                                         m_osRasterTable.c_str(),
    3684          20 :                                         m_nZoomLevel - k + 1, m_nZoomLevel - k);
    3685          20 :                     eErr = SQLCommand(hDB, pszSQL);
    3686          20 :                     sqlite3_free(pszSQL);
    3687          20 :                     if (eErr != OGRERR_NONE)
    3688             :                     {
    3689           0 :                         SoftRollbackTransaction();
    3690           0 :                         return CE_Failure;
    3691             :                     }
    3692             :                 }
    3693             : 
    3694           4 :                 double dfGDALMinX = m_adfGeoTransform[0];
    3695             :                 double dfGDALMinY =
    3696           4 :                     m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
    3697             :                 double dfGDALMaxX =
    3698           4 :                     m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
    3699           4 :                 double dfGDALMaxY = m_adfGeoTransform[3];
    3700           4 :                 double dfPixelXSizeZoomLevel = m_adfGeoTransform[1] * nOvFactor;
    3701             :                 double dfPixelYSizeZoomLevel =
    3702           4 :                     fabs(m_adfGeoTransform[5]) * nOvFactor;
    3703             :                 int nTileWidth, nTileHeight;
    3704           4 :                 GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3705           4 :                 int nTileMatrixWidth = (nOvXSize + nTileWidth - 1) / nTileWidth;
    3706           4 :                 int nTileMatrixHeight =
    3707           4 :                     (nOvYSize + nTileHeight - 1) / nTileHeight;
    3708           4 :                 pszSQL = sqlite3_mprintf(
    3709             :                     "INSERT INTO gpkg_tile_matrix "
    3710             :                     "(table_name,zoom_level,matrix_width,matrix_height,tile_"
    3711             :                     "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
    3712             :                     "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
    3713             :                     m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
    3714             :                     nTileMatrixHeight, nTileWidth, nTileHeight,
    3715             :                     dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
    3716           4 :                 eErr = SQLCommand(hDB, pszSQL);
    3717           4 :                 sqlite3_free(pszSQL);
    3718           4 :                 if (eErr != OGRERR_NONE)
    3719             :                 {
    3720           0 :                     SoftRollbackTransaction();
    3721           0 :                     return CE_Failure;
    3722             :                 }
    3723             : 
    3724           4 :                 SoftCommitTransaction();
    3725             : 
    3726           4 :                 m_nZoomLevel++; /* this change our zoom level as well as
    3727             :                                    previous overviews */
    3728          20 :                 for (int k = 0; k < jCandidate; k++)
    3729          16 :                     m_apoOverviewDS[k]->m_nZoomLevel++;
    3730             : 
    3731           4 :                 auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
    3732           4 :                 poOvrDS->ShareLockWithParentDataset(this);
    3733           4 :                 poOvrDS->InitRaster(
    3734             :                     this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
    3735             :                     m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
    3736             :                     nTileWidth, nTileHeight, nTileMatrixWidth,
    3737             :                     nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
    3738             :                     dfGDALMaxY);
    3739           4 :                 m_apoOverviewDS.insert(m_apoOverviewDS.begin() + jCandidate,
    3740           8 :                                        std::move(poOvrDS));
    3741             :             }
    3742             :         }
    3743             :     }
    3744             : 
    3745             :     GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
    3746          13 :         CPLCalloc(sizeof(GDALRasterBand **), nBands));
    3747          13 :     CPLErr eErr = CE_None;
    3748          49 :     for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
    3749             :     {
    3750          72 :         papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
    3751          36 :             CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
    3752          36 :         int iCurOverview = 0;
    3753         185 :         for (int i = 0; i < nOverviews; i++)
    3754             :         {
    3755         149 :             bool bFound = false;
    3756         724 :             for (const auto &poODS : m_apoOverviewDS)
    3757             :             {
    3758             :                 const int nOvFactor = static_cast<int>(
    3759         724 :                     0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
    3760             : 
    3761         724 :                 if (nOvFactor == panOverviewList[i])
    3762             :                 {
    3763         298 :                     papapoOverviewBands[iBand][iCurOverview] =
    3764         149 :                         poODS->GetRasterBand(iBand + 1);
    3765         149 :                     iCurOverview++;
    3766         149 :                     bFound = true;
    3767         149 :                     break;
    3768             :                 }
    3769             :             }
    3770         149 :             if (!bFound)
    3771             :             {
    3772           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3773             :                          "Could not find dataset corresponding to ov factor %d",
    3774           0 :                          panOverviewList[i]);
    3775           0 :                 eErr = CE_Failure;
    3776             :             }
    3777             :         }
    3778          36 :         if (eErr == CE_None)
    3779             :         {
    3780          36 :             CPLAssert(iCurOverview == nOverviews);
    3781             :         }
    3782             :     }
    3783             : 
    3784          13 :     if (eErr == CE_None)
    3785          13 :         eErr = GDALRegenerateOverviewsMultiBand(
    3786          13 :             nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
    3787             :             pfnProgress, pProgressData, papszOptions);
    3788             : 
    3789          49 :     for (int iBand = 0; iBand < nBands; iBand++)
    3790             :     {
    3791          36 :         CPLFree(papapoOverviewBands[iBand]);
    3792             :     }
    3793          13 :     CPLFree(papapoOverviewBands);
    3794             : 
    3795          13 :     return eErr;
    3796             : }
    3797             : 
    3798             : /************************************************************************/
    3799             : /*                            GetFileList()                             */
    3800             : /************************************************************************/
    3801             : 
    3802          36 : char **GDALGeoPackageDataset::GetFileList()
    3803             : {
    3804          36 :     TryLoadXML();
    3805          36 :     return GDALPamDataset::GetFileList();
    3806             : }
    3807             : 
    3808             : /************************************************************************/
    3809             : /*                      GetMetadataDomainList()                         */
    3810             : /************************************************************************/
    3811             : 
    3812          42 : char **GDALGeoPackageDataset::GetMetadataDomainList()
    3813             : {
    3814          42 :     GetMetadata();
    3815          42 :     if (!m_osRasterTable.empty())
    3816           5 :         GetMetadata("GEOPACKAGE");
    3817          42 :     return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
    3818          42 :                                    TRUE, "SUBDATASETS", nullptr);
    3819             : }
    3820             : 
    3821             : /************************************************************************/
    3822             : /*                        CheckMetadataDomain()                         */
    3823             : /************************************************************************/
    3824             : 
    3825        5163 : const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
    3826             : {
    3827        5346 :     if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
    3828         183 :         m_osRasterTable.empty())
    3829             :     {
    3830           4 :         CPLError(
    3831             :             CE_Warning, CPLE_IllegalArg,
    3832             :             "Using GEOPACKAGE for a non-raster geopackage is not supported. "
    3833             :             "Using default domain instead");
    3834           4 :         return nullptr;
    3835             :     }
    3836        5159 :     return pszDomain;
    3837             : }
    3838             : 
    3839             : /************************************************************************/
    3840             : /*                           HasMetadataTables()                        */
    3841             : /************************************************************************/
    3842             : 
    3843        5234 : bool GDALGeoPackageDataset::HasMetadataTables() const
    3844             : {
    3845        5234 :     if (m_nHasMetadataTables < 0)
    3846             :     {
    3847             :         const int nCount =
    3848        1937 :             SQLGetInteger(hDB,
    3849             :                           "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
    3850             :                           "('gpkg_metadata', 'gpkg_metadata_reference') "
    3851             :                           "AND type IN ('table', 'view')",
    3852             :                           nullptr);
    3853        1937 :         m_nHasMetadataTables = nCount == 2;
    3854             :     }
    3855        5234 :     return CPL_TO_BOOL(m_nHasMetadataTables);
    3856             : }
    3857             : 
    3858             : /************************************************************************/
    3859             : /*                         HasDataColumnsTable()                        */
    3860             : /************************************************************************/
    3861             : 
    3862        1156 : bool GDALGeoPackageDataset::HasDataColumnsTable() const
    3863             : {
    3864        2312 :     const int nCount = SQLGetInteger(
    3865        1156 :         hDB,
    3866             :         "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
    3867             :         "AND type IN ('table', 'view')",
    3868             :         nullptr);
    3869        1156 :     return nCount == 1;
    3870             : }
    3871             : 
    3872             : /************************************************************************/
    3873             : /*                    HasDataColumnConstraintsTable()                   */
    3874             : /************************************************************************/
    3875             : 
    3876         119 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
    3877             : {
    3878         119 :     const int nCount = SQLGetInteger(hDB,
    3879             :                                      "SELECT 1 FROM sqlite_master WHERE name = "
    3880             :                                      "'gpkg_data_column_constraints'"
    3881             :                                      "AND type IN ('table', 'view')",
    3882             :                                      nullptr);
    3883         119 :     return nCount == 1;
    3884             : }
    3885             : 
    3886             : /************************************************************************/
    3887             : /*                  HasDataColumnConstraintsTableGPKG_1_0()             */
    3888             : /************************************************************************/
    3889             : 
    3890          73 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
    3891             : {
    3892          73 :     if (m_nApplicationId != GP10_APPLICATION_ID)
    3893          71 :         return false;
    3894             :     // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
    3895             :     // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
    3896           2 :     bool bRet = false;
    3897           2 :     sqlite3_stmt *hSQLStmt = nullptr;
    3898           2 :     int rc = sqlite3_prepare_v2(hDB,
    3899             :                                 "SELECT minIsInclusive, maxIsInclusive FROM "
    3900             :                                 "gpkg_data_column_constraints",
    3901             :                                 -1, &hSQLStmt, nullptr);
    3902           2 :     if (rc == SQLITE_OK)
    3903             :     {
    3904           2 :         bRet = true;
    3905           2 :         sqlite3_finalize(hSQLStmt);
    3906             :     }
    3907           2 :     return bRet;
    3908             : }
    3909             : 
    3910             : /************************************************************************/
    3911             : /*      CreateColumnsTableAndColumnConstraintsTablesIfNecessary()       */
    3912             : /************************************************************************/
    3913             : 
    3914          49 : bool GDALGeoPackageDataset::
    3915             :     CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
    3916             : {
    3917          49 :     if (!HasDataColumnsTable())
    3918             :     {
    3919             :         // Geopackage < 1.3 had
    3920             :         // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
    3921             :         // gpkg_contents(table_name) instead of the unique constraint.
    3922          10 :         if (OGRERR_NONE !=
    3923          10 :             SQLCommand(
    3924             :                 GetDB(),
    3925             :                 "CREATE TABLE gpkg_data_columns ("
    3926             :                 "table_name TEXT NOT NULL,"
    3927             :                 "column_name TEXT NOT NULL,"
    3928             :                 "name TEXT,"
    3929             :                 "title TEXT,"
    3930             :                 "description TEXT,"
    3931             :                 "mime_type TEXT,"
    3932             :                 "constraint_name TEXT,"
    3933             :                 "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
    3934             :                 "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
    3935             :         {
    3936           0 :             return false;
    3937             :         }
    3938             :     }
    3939          49 :     if (!HasDataColumnConstraintsTable())
    3940             :     {
    3941          22 :         const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
    3942          11 :                                            ? "min_is_inclusive"
    3943             :                                            : "minIsInclusive";
    3944          22 :         const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
    3945          11 :                                            ? "max_is_inclusive"
    3946             :                                            : "maxIsInclusive";
    3947             : 
    3948             :         const std::string osSQL(
    3949             :             CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
    3950             :                        "constraint_name TEXT NOT NULL,"
    3951             :                        "constraint_type TEXT NOT NULL,"
    3952             :                        "value TEXT,"
    3953             :                        "min NUMERIC,"
    3954             :                        "%s BOOLEAN,"
    3955             :                        "max NUMERIC,"
    3956             :                        "%s BOOLEAN,"
    3957             :                        "description TEXT,"
    3958             :                        "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
    3959             :                        "constraint_type, value));",
    3960          11 :                        min_is_inclusive, max_is_inclusive));
    3961          11 :         if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
    3962             :         {
    3963           0 :             return false;
    3964             :         }
    3965             :     }
    3966          49 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    3967             :     {
    3968           0 :         return false;
    3969             :     }
    3970          49 :     if (SQLGetInteger(GetDB(),
    3971             :                       "SELECT 1 FROM gpkg_extensions WHERE "
    3972             :                       "table_name = 'gpkg_data_columns'",
    3973          49 :                       nullptr) != 1)
    3974             :     {
    3975          11 :         if (OGRERR_NONE !=
    3976          11 :             SQLCommand(
    3977             :                 GetDB(),
    3978             :                 "INSERT INTO gpkg_extensions "
    3979             :                 "(table_name,column_name,extension_name,definition,scope) "
    3980             :                 "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
    3981             :                 "'http://www.geopackage.org/spec121/#extension_schema', "
    3982             :                 "'read-write')"))
    3983             :         {
    3984           0 :             return false;
    3985             :         }
    3986             :     }
    3987          49 :     if (SQLGetInteger(GetDB(),
    3988             :                       "SELECT 1 FROM gpkg_extensions WHERE "
    3989             :                       "table_name = 'gpkg_data_column_constraints'",
    3990          49 :                       nullptr) != 1)
    3991             :     {
    3992          11 :         if (OGRERR_NONE !=
    3993          11 :             SQLCommand(
    3994             :                 GetDB(),
    3995             :                 "INSERT INTO gpkg_extensions "
    3996             :                 "(table_name,column_name,extension_name,definition,scope) "
    3997             :                 "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
    3998             :                 "'http://www.geopackage.org/spec121/#extension_schema', "
    3999             :                 "'read-write')"))
    4000             :         {
    4001           0 :             return false;
    4002             :         }
    4003             :     }
    4004             : 
    4005          49 :     return true;
    4006             : }
    4007             : 
    4008             : /************************************************************************/
    4009             : /*                        HasGpkgextRelationsTable()                    */
    4010             : /************************************************************************/
    4011             : 
    4012        1140 : bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
    4013             : {
    4014        2280 :     const int nCount = SQLGetInteger(
    4015        1140 :         hDB,
    4016             :         "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
    4017             :         "AND type IN ('table', 'view')",
    4018             :         nullptr);
    4019        1140 :     return nCount == 1;
    4020             : }
    4021             : 
    4022             : /************************************************************************/
    4023             : /*                    CreateRelationsTableIfNecessary()                 */
    4024             : /************************************************************************/
    4025             : 
    4026           9 : bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
    4027             : {
    4028           9 :     if (HasGpkgextRelationsTable())
    4029             :     {
    4030           5 :         return true;
    4031             :     }
    4032             : 
    4033           4 :     if (OGRERR_NONE !=
    4034           4 :         SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
    4035             :                             "id INTEGER PRIMARY KEY AUTOINCREMENT,"
    4036             :                             "base_table_name TEXT NOT NULL,"
    4037             :                             "base_primary_column TEXT NOT NULL DEFAULT 'id',"
    4038             :                             "related_table_name TEXT NOT NULL,"
    4039             :                             "related_primary_column TEXT NOT NULL DEFAULT 'id',"
    4040             :                             "relation_name TEXT NOT NULL,"
    4041             :                             "mapping_table_name TEXT NOT NULL UNIQUE);"))
    4042             :     {
    4043           0 :         return false;
    4044             :     }
    4045             : 
    4046           4 :     return true;
    4047             : }
    4048             : 
    4049             : /************************************************************************/
    4050             : /*                        HasQGISLayerStyles()                          */
    4051             : /************************************************************************/
    4052             : 
    4053          11 : bool GDALGeoPackageDataset::HasQGISLayerStyles() const
    4054             : {
    4055             :     // QGIS layer_styles extension:
    4056             :     // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
    4057          11 :     bool bRet = false;
    4058             :     const int nCount =
    4059          11 :         SQLGetInteger(hDB,
    4060             :                       "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
    4061             :                       "AND type = 'table'",
    4062             :                       nullptr);
    4063          11 :     if (nCount == 1)
    4064             :     {
    4065           1 :         sqlite3_stmt *hSQLStmt = nullptr;
    4066           2 :         int rc = sqlite3_prepare_v2(
    4067           1 :             hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
    4068             :             &hSQLStmt, nullptr);
    4069           1 :         if (rc == SQLITE_OK)
    4070             :         {
    4071           1 :             bRet = true;
    4072           1 :             sqlite3_finalize(hSQLStmt);
    4073             :         }
    4074             :     }
    4075          11 :     return bRet;
    4076             : }
    4077             : 
    4078             : /************************************************************************/
    4079             : /*                            GetMetadata()                             */
    4080             : /************************************************************************/
    4081             : 
    4082        3470 : char **GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
    4083             : 
    4084             : {
    4085        3470 :     pszDomain = CheckMetadataDomain(pszDomain);
    4086        3470 :     if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
    4087          61 :         return m_aosSubDatasets.List();
    4088             : 
    4089        3409 :     if (m_bHasReadMetadataFromStorage)
    4090        1521 :         return GDALPamDataset::GetMetadata(pszDomain);
    4091             : 
    4092        1888 :     m_bHasReadMetadataFromStorage = true;
    4093             : 
    4094        1888 :     TryLoadXML();
    4095             : 
    4096        1888 :     if (!HasMetadataTables())
    4097        1389 :         return GDALPamDataset::GetMetadata(pszDomain);
    4098             : 
    4099         499 :     char *pszSQL = nullptr;
    4100         499 :     if (!m_osRasterTable.empty())
    4101             :     {
    4102         169 :         pszSQL = sqlite3_mprintf(
    4103             :             "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
    4104             :             "mdr.reference_scope FROM gpkg_metadata md "
    4105             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4106             :             "WHERE "
    4107             :             "(mdr.reference_scope = 'geopackage' OR "
    4108             :             "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
    4109             :             "lower('%q'))) ORDER BY md.id "
    4110             :             "LIMIT 1000",  // to avoid denial of service
    4111             :             m_osRasterTable.c_str());
    4112             :     }
    4113             :     else
    4114             :     {
    4115         330 :         pszSQL = sqlite3_mprintf(
    4116             :             "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
    4117             :             "mdr.reference_scope FROM gpkg_metadata md "
    4118             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4119             :             "WHERE "
    4120             :             "mdr.reference_scope = 'geopackage' ORDER BY md.id "
    4121             :             "LIMIT 1000"  // to avoid denial of service
    4122             :         );
    4123             :     }
    4124             : 
    4125         998 :     auto oResult = SQLQuery(hDB, pszSQL);
    4126         499 :     sqlite3_free(pszSQL);
    4127         499 :     if (!oResult)
    4128             :     {
    4129           0 :         return GDALPamDataset::GetMetadata(pszDomain);
    4130             :     }
    4131             : 
    4132         499 :     char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
    4133             : 
    4134             :     /* GDAL metadata */
    4135         688 :     for (int i = 0; i < oResult->RowCount(); i++)
    4136             :     {
    4137         189 :         const char *pszMetadata = oResult->GetValue(0, i);
    4138         189 :         const char *pszMDStandardURI = oResult->GetValue(1, i);
    4139         189 :         const char *pszMimeType = oResult->GetValue(2, i);
    4140         189 :         const char *pszReferenceScope = oResult->GetValue(3, i);
    4141         189 :         if (pszMetadata && pszMDStandardURI && pszMimeType &&
    4142         189 :             pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
    4143         173 :             EQUAL(pszMimeType, "text/xml"))
    4144             :         {
    4145         173 :             CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
    4146         173 :             if (psXMLNode)
    4147             :             {
    4148         346 :                 GDALMultiDomainMetadata oLocalMDMD;
    4149         173 :                 oLocalMDMD.XMLInit(psXMLNode, FALSE);
    4150         331 :                 if (!m_osRasterTable.empty() &&
    4151         158 :                     EQUAL(pszReferenceScope, "geopackage"))
    4152             :                 {
    4153           6 :                     oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
    4154             :                 }
    4155             :                 else
    4156             :                 {
    4157             :                     papszMetadata =
    4158         167 :                         CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
    4159         167 :                     CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
    4160         167 :                     CSLConstList papszIter = papszDomainList;
    4161         444 :                     while (papszIter && *papszIter)
    4162             :                     {
    4163         277 :                         if (EQUAL(*papszIter, "IMAGE_STRUCTURE"))
    4164             :                         {
    4165             :                             CSLConstList papszMD =
    4166         125 :                                 oLocalMDMD.GetMetadata(*papszIter);
    4167             :                             const char *pszBAND_COUNT =
    4168         125 :                                 CSLFetchNameValue(papszMD, "BAND_COUNT");
    4169         125 :                             if (pszBAND_COUNT)
    4170         123 :                                 m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
    4171             : 
    4172             :                             const char *pszCOLOR_TABLE =
    4173         125 :                                 CSLFetchNameValue(papszMD, "COLOR_TABLE");
    4174         125 :                             if (pszCOLOR_TABLE)
    4175             :                             {
    4176             :                                 const CPLStringList aosTokens(
    4177             :                                     CSLTokenizeString2(pszCOLOR_TABLE, "{,",
    4178          26 :                                                        0));
    4179          13 :                                 if ((aosTokens.size() % 4) == 0)
    4180             :                                 {
    4181          13 :                                     const int nColors = aosTokens.size() / 4;
    4182             :                                     m_poCTFromMetadata =
    4183          13 :                                         std::make_unique<GDALColorTable>();
    4184        3341 :                                     for (int iColor = 0; iColor < nColors;
    4185             :                                          ++iColor)
    4186             :                                     {
    4187             :                                         GDALColorEntry sEntry;
    4188        3328 :                                         sEntry.c1 = static_cast<short>(
    4189        3328 :                                             atoi(aosTokens[4 * iColor + 0]));
    4190        3328 :                                         sEntry.c2 = static_cast<short>(
    4191        3328 :                                             atoi(aosTokens[4 * iColor + 1]));
    4192        3328 :                                         sEntry.c3 = static_cast<short>(
    4193        3328 :                                             atoi(aosTokens[4 * iColor + 2]));
    4194        3328 :                                         sEntry.c4 = static_cast<short>(
    4195        3328 :                                             atoi(aosTokens[4 * iColor + 3]));
    4196        3328 :                                         m_poCTFromMetadata->SetColorEntry(
    4197             :                                             iColor, &sEntry);
    4198             :                                     }
    4199             :                                 }
    4200             :                             }
    4201             : 
    4202             :                             const char *pszTILE_FORMAT =
    4203         125 :                                 CSLFetchNameValue(papszMD, "TILE_FORMAT");
    4204         125 :                             if (pszTILE_FORMAT)
    4205             :                             {
    4206           8 :                                 m_osTFFromMetadata = pszTILE_FORMAT;
    4207           8 :                                 oMDMD.SetMetadataItem("TILE_FORMAT",
    4208             :                                                       pszTILE_FORMAT,
    4209             :                                                       "IMAGE_STRUCTURE");
    4210             :                             }
    4211             : 
    4212             :                             const char *pszNodataValue =
    4213         125 :                                 CSLFetchNameValue(papszMD, "NODATA_VALUE");
    4214         125 :                             if (pszNodataValue)
    4215             :                             {
    4216           2 :                                 m_osNodataValueFromMetadata = pszNodataValue;
    4217             :                             }
    4218             :                         }
    4219             : 
    4220         152 :                         else if (!EQUAL(*papszIter, "") &&
    4221          16 :                                  !STARTS_WITH(*papszIter, "BAND_"))
    4222             :                         {
    4223          12 :                             oMDMD.SetMetadata(
    4224           6 :                                 oLocalMDMD.GetMetadata(*papszIter), *papszIter);
    4225             :                         }
    4226         277 :                         papszIter++;
    4227             :                     }
    4228             :                 }
    4229         173 :                 CPLDestroyXMLNode(psXMLNode);
    4230             :             }
    4231             :         }
    4232             :     }
    4233             : 
    4234         499 :     GDALPamDataset::SetMetadata(papszMetadata);
    4235         499 :     CSLDestroy(papszMetadata);
    4236         499 :     papszMetadata = nullptr;
    4237             : 
    4238             :     /* Add non-GDAL metadata now */
    4239         499 :     int nNonGDALMDILocal = 1;
    4240         499 :     int nNonGDALMDIGeopackage = 1;
    4241         688 :     for (int i = 0; i < oResult->RowCount(); i++)
    4242             :     {
    4243         189 :         const char *pszMetadata = oResult->GetValue(0, i);
    4244         189 :         const char *pszMDStandardURI = oResult->GetValue(1, i);
    4245         189 :         const char *pszMimeType = oResult->GetValue(2, i);
    4246         189 :         const char *pszReferenceScope = oResult->GetValue(3, i);
    4247         189 :         if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
    4248         189 :             pszMimeType == nullptr || pszReferenceScope == nullptr)
    4249             :         {
    4250             :             // should not happen as there are NOT NULL constraints
    4251             :             // But a database could lack such NOT NULL constraints or have
    4252             :             // large values that would cause a memory allocation failure.
    4253           0 :             continue;
    4254             :         }
    4255         189 :         int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
    4256         189 :         if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
    4257         173 :             EQUAL(pszMimeType, "text/xml"))
    4258         173 :             continue;
    4259             : 
    4260          16 :         if (!m_osRasterTable.empty() && bIsGPKGScope)
    4261             :         {
    4262           8 :             oMDMD.SetMetadataItem(
    4263             :                 CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
    4264             :                 pszMetadata, "GEOPACKAGE");
    4265           8 :             nNonGDALMDIGeopackage++;
    4266             :         }
    4267             :         /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
    4268             :         ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
    4269             :         {
    4270             :             char* apszMD[2];
    4271             :             apszMD[0] = (char*)pszMetadata;
    4272             :             apszMD[1] = NULL;
    4273             :             oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
    4274             :         }*/
    4275             :         else
    4276             :         {
    4277           8 :             oMDMD.SetMetadataItem(
    4278             :                 CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
    4279             :                 pszMetadata);
    4280           8 :             nNonGDALMDILocal++;
    4281             :         }
    4282             :     }
    4283             : 
    4284         499 :     return GDALPamDataset::GetMetadata(pszDomain);
    4285             : }
    4286             : 
    4287             : /************************************************************************/
    4288             : /*                            WriteMetadata()                           */
    4289             : /************************************************************************/
    4290             : 
    4291         736 : void GDALGeoPackageDataset::WriteMetadata(
    4292             :     CPLXMLNode *psXMLNode, /* will be destroyed by the method */
    4293             :     const char *pszTableName)
    4294             : {
    4295         736 :     const bool bIsEmpty = (psXMLNode == nullptr);
    4296         736 :     if (!HasMetadataTables())
    4297             :     {
    4298         528 :         if (bIsEmpty || !CreateMetadataTables())
    4299             :         {
    4300         251 :             CPLDestroyXMLNode(psXMLNode);
    4301         251 :             return;
    4302             :         }
    4303             :     }
    4304             : 
    4305         485 :     char *pszXML = nullptr;
    4306         485 :     if (!bIsEmpty)
    4307             :     {
    4308             :         CPLXMLNode *psMasterXMLNode =
    4309         324 :             CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
    4310         324 :         psMasterXMLNode->psChild = psXMLNode;
    4311         324 :         pszXML = CPLSerializeXMLTree(psMasterXMLNode);
    4312         324 :         CPLDestroyXMLNode(psMasterXMLNode);
    4313             :     }
    4314             :     // cppcheck-suppress uselessAssignmentPtrArg
    4315         485 :     psXMLNode = nullptr;
    4316             : 
    4317         485 :     char *pszSQL = nullptr;
    4318         485 :     if (pszTableName && pszTableName[0] != '\0')
    4319             :     {
    4320         337 :         pszSQL = sqlite3_mprintf(
    4321             :             "SELECT md.id FROM gpkg_metadata md "
    4322             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4323             :             "WHERE md.md_scope = 'dataset' AND "
    4324             :             "md.md_standard_uri='http://gdal.org' "
    4325             :             "AND md.mime_type='text/xml' AND mdr.reference_scope = 'table' AND "
    4326             :             "lower(mdr.table_name) = lower('%q')",
    4327             :             pszTableName);
    4328             :     }
    4329             :     else
    4330             :     {
    4331         148 :         pszSQL = sqlite3_mprintf(
    4332             :             "SELECT md.id FROM gpkg_metadata md "
    4333             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4334             :             "WHERE md.md_scope = 'dataset' AND "
    4335             :             "md.md_standard_uri='http://gdal.org' "
    4336             :             "AND md.mime_type='text/xml' AND mdr.reference_scope = "
    4337             :             "'geopackage'");
    4338             :     }
    4339             :     OGRErr err;
    4340         485 :     int mdId = SQLGetInteger(hDB, pszSQL, &err);
    4341         485 :     if (err != OGRERR_NONE)
    4342         453 :         mdId = -1;
    4343         485 :     sqlite3_free(pszSQL);
    4344             : 
    4345         485 :     if (bIsEmpty)
    4346             :     {
    4347         161 :         if (mdId >= 0)
    4348             :         {
    4349           6 :             SQLCommand(
    4350             :                 hDB,
    4351             :                 CPLSPrintf(
    4352             :                     "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
    4353             :                     mdId));
    4354           6 :             SQLCommand(
    4355             :                 hDB,
    4356             :                 CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
    4357             :         }
    4358             :     }
    4359             :     else
    4360             :     {
    4361         324 :         if (mdId >= 0)
    4362             :         {
    4363          26 :             pszSQL = sqlite3_mprintf(
    4364             :                 "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
    4365             :                 pszXML, mdId);
    4366             :         }
    4367             :         else
    4368             :         {
    4369             :             pszSQL =
    4370         298 :                 sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
    4371             :                                 "md_standard_uri, mime_type, metadata) VALUES "
    4372             :                                 "('dataset','http://gdal.org','text/xml','%q')",
    4373             :                                 pszXML);
    4374             :         }
    4375         324 :         SQLCommand(hDB, pszSQL);
    4376         324 :         sqlite3_free(pszSQL);
    4377             : 
    4378         324 :         CPLFree(pszXML);
    4379             : 
    4380         324 :         if (mdId < 0)
    4381             :         {
    4382         298 :             const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
    4383         298 :             if (pszTableName != nullptr && pszTableName[0] != '\0')
    4384             :             {
    4385         286 :                 pszSQL = sqlite3_mprintf(
    4386             :                     "INSERT INTO gpkg_metadata_reference (reference_scope, "
    4387             :                     "table_name, timestamp, md_file_id) VALUES "
    4388             :                     "('table', '%q', %s, %d)",
    4389         572 :                     pszTableName, GetCurrentDateEscapedSQL().c_str(),
    4390             :                     static_cast<int>(nFID));
    4391             :             }
    4392             :             else
    4393             :             {
    4394          12 :                 pszSQL = sqlite3_mprintf(
    4395             :                     "INSERT INTO gpkg_metadata_reference (reference_scope, "
    4396             :                     "timestamp, md_file_id) VALUES "
    4397             :                     "('geopackage', %s, %d)",
    4398          24 :                     GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
    4399             :             }
    4400             :         }
    4401             :         else
    4402             :         {
    4403          26 :             pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
    4404             :                                      "timestamp = %s WHERE md_file_id = %d",
    4405          52 :                                      GetCurrentDateEscapedSQL().c_str(), mdId);
    4406             :         }
    4407         324 :         SQLCommand(hDB, pszSQL);
    4408         324 :         sqlite3_free(pszSQL);
    4409             :     }
    4410             : }
    4411             : 
    4412             : /************************************************************************/
    4413             : /*                        CreateMetadataTables()                        */
    4414             : /************************************************************************/
    4415             : 
    4416         295 : bool GDALGeoPackageDataset::CreateMetadataTables()
    4417             : {
    4418             :     const bool bCreateTriggers =
    4419         295 :         CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
    4420             : 
    4421             :     /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL  */
    4422             :     CPLString osSQL = "CREATE TABLE gpkg_metadata ("
    4423             :                       "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
    4424             :                       "md_scope TEXT NOT NULL DEFAULT 'dataset',"
    4425             :                       "md_standard_uri TEXT NOT NULL,"
    4426             :                       "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
    4427             :                       "metadata TEXT NOT NULL DEFAULT ''"
    4428         590 :                       ")";
    4429             : 
    4430             :     /* From D.2. metadata Table 40. metadata Trigger Definition SQL  */
    4431         295 :     const char *pszMetadataTriggers =
    4432             :         "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
    4433             :         "BEFORE INSERT ON 'gpkg_metadata' "
    4434             :         "FOR EACH ROW BEGIN "
    4435             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
    4436             :         "constraint: md_scope must be one of undefined | fieldSession | "
    4437             :         "collectionSession | series | dataset | featureType | feature | "
    4438             :         "attributeType | attribute | tile | model | catalogue | schema | "
    4439             :         "taxonomy software | service | collectionHardware | "
    4440             :         "nonGeographicDataset | dimensionGroup') "
    4441             :         "WHERE NOT(NEW.md_scope IN "
    4442             :         "('undefined','fieldSession','collectionSession','series','dataset', "
    4443             :         "'featureType','feature','attributeType','attribute','tile','model', "
    4444             :         "'catalogue','schema','taxonomy','software','service', "
    4445             :         "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
    4446             :         "END; "
    4447             :         "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
    4448             :         "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
    4449             :         "FOR EACH ROW BEGIN "
    4450             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
    4451             :         "constraint: md_scope must be one of undefined | fieldSession | "
    4452             :         "collectionSession | series | dataset | featureType | feature | "
    4453             :         "attributeType | attribute | tile | model | catalogue | schema | "
    4454             :         "taxonomy software | service | collectionHardware | "
    4455             :         "nonGeographicDataset | dimensionGroup') "
    4456             :         "WHERE NOT(NEW.md_scope IN "
    4457             :         "('undefined','fieldSession','collectionSession','series','dataset', "
    4458             :         "'featureType','feature','attributeType','attribute','tile','model', "
    4459             :         "'catalogue','schema','taxonomy','software','service', "
    4460             :         "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
    4461             :         "END";
    4462         295 :     if (bCreateTriggers)
    4463             :     {
    4464           0 :         osSQL += ";";
    4465           0 :         osSQL += pszMetadataTriggers;
    4466             :     }
    4467             : 
    4468             :     /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
    4469             :      * Table Definition SQL */
    4470             :     osSQL += ";"
    4471             :              "CREATE TABLE gpkg_metadata_reference ("
    4472             :              "reference_scope TEXT NOT NULL,"
    4473             :              "table_name TEXT,"
    4474             :              "column_name TEXT,"
    4475             :              "row_id_value INTEGER,"
    4476             :              "timestamp DATETIME NOT NULL DEFAULT "
    4477             :              "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
    4478             :              "md_file_id INTEGER NOT NULL,"
    4479             :              "md_parent_id INTEGER,"
    4480             :              "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
    4481             :              "gpkg_metadata(id),"
    4482             :              "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
    4483             :              "gpkg_metadata(id)"
    4484         295 :              ")";
    4485             : 
    4486             :     /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
    4487             :      * Definition SQL   */
    4488         295 :     const char *pszMetadataReferenceTriggers =
    4489             :         "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_insert' "
    4490             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4491             :         "FOR EACH ROW BEGIN "
    4492             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4493             :         "violates constraint: reference_scope must be one of \"geopackage\", "
    4494             :         "table\", \"column\", \"row\", \"row/col\"') "
    4495             :         "WHERE NOT NEW.reference_scope IN "
    4496             :         "('geopackage','table','column','row','row/col'); "
    4497             :         "END; "
    4498             :         "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
    4499             :         "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
    4500             :         "FOR EACH ROW BEGIN "
    4501             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4502             :         "violates constraint: reference_scope must be one of \"geopackage\", "
    4503             :         "\"table\", \"column\", \"row\", \"row/col\"') "
    4504             :         "WHERE NOT NEW.reference_scope IN "
    4505             :         "('geopackage','table','column','row','row/col'); "
    4506             :         "END; "
    4507             :         "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
    4508             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4509             :         "FOR EACH ROW BEGIN "
    4510             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4511             :         "violates constraint: column name must be NULL when reference_scope "
    4512             :         "is \"geopackage\", \"table\" or \"row\"') "
    4513             :         "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
    4514             :         "AND NEW.column_name IS NOT NULL); "
    4515             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4516             :         "violates constraint: column name must be defined for the specified "
    4517             :         "table when reference_scope is \"column\" or \"row/col\"') "
    4518             :         "WHERE (NEW.reference_scope IN ('column','row/col') "
    4519             :         "AND NOT NEW.table_name IN ( "
    4520             :         "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
    4521             :         "AND name = NEW.table_name "
    4522             :         "AND sql LIKE ('%' || NEW.column_name || '%'))); "
    4523             :         "END; "
    4524             :         "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
    4525             :         "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
    4526             :         "FOR EACH ROW BEGIN "
    4527             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4528             :         "violates constraint: column name must be NULL when reference_scope "
    4529             :         "is \"geopackage\", \"table\" or \"row\"') "
    4530             :         "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
    4531             :         "AND NEW.column_name IS NOT NULL); "
    4532             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4533             :         "violates constraint: column name must be defined for the specified "
    4534             :         "table when reference_scope is \"column\" or \"row/col\"') "
    4535             :         "WHERE (NEW.reference_scope IN ('column','row/col') "
    4536             :         "AND NOT NEW.table_name IN ( "
    4537             :         "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
    4538             :         "AND name = NEW.table_name "
    4539             :         "AND sql LIKE ('%' || NEW.column_name || '%'))); "
    4540             :         "END; "
    4541             :         "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_insert' "
    4542             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4543             :         "FOR EACH ROW BEGIN "
    4544             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4545             :         "violates constraint: row_id_value must be NULL when reference_scope "
    4546             :         "is \"geopackage\", \"table\" or \"column\"') "
    4547             :         "WHERE NEW.reference_scope IN ('geopackage','table','column') "
    4548             :         "AND NEW.row_id_value IS NOT NULL; "
    4549             :         "END; "
    4550             :         "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
    4551             :         "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
    4552             :         "FOR EACH ROW BEGIN "
    4553             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4554             :         "violates constraint: row_id_value must be NULL when reference_scope "
    4555             :         "is \"geopackage\", \"table\" or \"column\"') "
    4556             :         "WHERE NEW.reference_scope IN ('geopackage','table','column') "
    4557             :         "AND NEW.row_id_value IS NOT NULL; "
    4558             :         "END; "
    4559             :         "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
    4560             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4561             :         "FOR EACH ROW BEGIN "
    4562             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4563             :         "violates constraint: timestamp must be a valid time in ISO 8601 "
    4564             :         "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
    4565             :         "WHERE NOT (NEW.timestamp GLOB "
    4566             :         "'[1-2][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-"
    4567             :         "5][0-9].[0-9][0-9][0-9]Z' "
    4568             :         "AND strftime('%s',NEW.timestamp) NOT NULL); "
    4569             :         "END; "
    4570             :         "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
    4571             :         "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
    4572             :         "FOR EACH ROW BEGIN "
    4573             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4574             :         "violates constraint: timestamp must be a valid time in ISO 8601 "
    4575             :         "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
    4576             :         "WHERE NOT (NEW.timestamp GLOB "
    4577             :         "'[1-2][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-"
    4578             :         "5][0-9].[0-9][0-9][0-9]Z' "
    4579             :         "AND strftime('%s',NEW.timestamp) NOT NULL); "
    4580             :         "END";
    4581         295 :     if (bCreateTriggers)
    4582             :     {
    4583           0 :         osSQL += ";";
    4584           0 :         osSQL += pszMetadataReferenceTriggers;
    4585             :     }
    4586             : 
    4587         295 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    4588           2 :         return false;
    4589             : 
    4590         293 :     osSQL += ";";
    4591             :     osSQL += "INSERT INTO gpkg_extensions "
    4592             :              "(table_name, column_name, extension_name, definition, scope) "
    4593             :              "VALUES "
    4594             :              "('gpkg_metadata', NULL, 'gpkg_metadata', "
    4595             :              "'http://www.geopackage.org/spec120/#extension_metadata', "
    4596         293 :              "'read-write')";
    4597             : 
    4598         293 :     osSQL += ";";
    4599             :     osSQL += "INSERT INTO gpkg_extensions "
    4600             :              "(table_name, column_name, extension_name, definition, scope) "
    4601             :              "VALUES "
    4602             :              "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
    4603             :              "'http://www.geopackage.org/spec120/#extension_metadata', "
    4604         293 :              "'read-write')";
    4605             : 
    4606         293 :     const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
    4607         293 :     m_nHasMetadataTables = bOK;
    4608         293 :     return bOK;
    4609             : }
    4610             : 
    4611             : /************************************************************************/
    4612             : /*                            FlushMetadata()                           */
    4613             : /************************************************************************/
    4614             : 
    4615        8426 : void GDALGeoPackageDataset::FlushMetadata()
    4616             : {
    4617        8426 :     if (!m_bMetadataDirty || m_poParentDS != nullptr ||
    4618         370 :         m_nCreateMetadataTables == FALSE)
    4619        8062 :         return;
    4620         364 :     m_bMetadataDirty = false;
    4621             : 
    4622         364 :     if (eAccess == GA_ReadOnly)
    4623             :     {
    4624           3 :         return;
    4625             :     }
    4626             : 
    4627         361 :     bool bCanWriteAreaOrPoint =
    4628         720 :         !m_bGridCellEncodingAsCO &&
    4629         359 :         (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
    4630         361 :     if (!m_osRasterTable.empty())
    4631             :     {
    4632             :         const char *pszIdentifier =
    4633         142 :             GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
    4634             :         const char *pszDescription =
    4635         142 :             GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
    4636         171 :         if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
    4637          29 :             pszIdentifier != m_osIdentifier)
    4638             :         {
    4639          14 :             m_osIdentifier = pszIdentifier;
    4640             :             char *pszSQL =
    4641          14 :                 sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
    4642             :                                 "WHERE lower(table_name) = lower('%q')",
    4643             :                                 pszIdentifier, m_osRasterTable.c_str());
    4644          14 :             SQLCommand(hDB, pszSQL);
    4645          14 :             sqlite3_free(pszSQL);
    4646             :         }
    4647         149 :         if (!m_bDescriptionAsCO && pszDescription != nullptr &&
    4648           7 :             pszDescription != m_osDescription)
    4649             :         {
    4650           7 :             m_osDescription = pszDescription;
    4651             :             char *pszSQL =
    4652           7 :                 sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
    4653             :                                 "WHERE lower(table_name) = lower('%q')",
    4654             :                                 pszDescription, m_osRasterTable.c_str());
    4655           7 :             SQLCommand(hDB, pszSQL);
    4656           7 :             sqlite3_free(pszSQL);
    4657             :         }
    4658         142 :         if (bCanWriteAreaOrPoint)
    4659             :         {
    4660             :             const char *pszAreaOrPoint =
    4661          28 :                 GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
    4662          28 :             if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
    4663             :             {
    4664          23 :                 bCanWriteAreaOrPoint = false;
    4665          23 :                 char *pszSQL = sqlite3_mprintf(
    4666             :                     "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
    4667             :                     "grid_cell_encoding = 'grid-value-is-area' WHERE "
    4668             :                     "lower(tile_matrix_set_name) = lower('%q')",
    4669             :                     m_osRasterTable.c_str());
    4670          23 :                 SQLCommand(hDB, pszSQL);
    4671          23 :                 sqlite3_free(pszSQL);
    4672             :             }
    4673           5 :             else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
    4674             :             {
    4675           1 :                 bCanWriteAreaOrPoint = false;
    4676           1 :                 char *pszSQL = sqlite3_mprintf(
    4677             :                     "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
    4678             :                     "grid_cell_encoding = 'grid-value-is-center' WHERE "
    4679             :                     "lower(tile_matrix_set_name) = lower('%q')",
    4680             :                     m_osRasterTable.c_str());
    4681           1 :                 SQLCommand(hDB, pszSQL);
    4682           1 :                 sqlite3_free(pszSQL);
    4683             :             }
    4684             :         }
    4685             :     }
    4686             : 
    4687         361 :     char **papszMDDup = nullptr;
    4688         564 :     for (char **papszIter = GDALGeoPackageDataset::GetMetadata();
    4689         564 :          papszIter && *papszIter; ++papszIter)
    4690             :     {
    4691         203 :         if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
    4692          29 :             continue;
    4693         174 :         if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
    4694           8 :             continue;
    4695         166 :         if (STARTS_WITH_CI(*papszIter, "ZOOM_LEVEL="))
    4696          14 :             continue;
    4697         152 :         if (STARTS_WITH_CI(*papszIter, "GPKG_METADATA_ITEM_"))
    4698           4 :             continue;
    4699         148 :         if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
    4700          29 :             !bCanWriteAreaOrPoint &&
    4701          26 :             STARTS_WITH_CI(*papszIter, GDALMD_AREA_OR_POINT))
    4702             :         {
    4703          26 :             continue;
    4704             :         }
    4705         122 :         papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4706             :     }
    4707             : 
    4708         361 :     CPLXMLNode *psXMLNode = nullptr;
    4709             :     {
    4710         361 :         GDALMultiDomainMetadata oLocalMDMD;
    4711         361 :         CSLConstList papszDomainList = oMDMD.GetDomainList();
    4712         361 :         CSLConstList papszIter = papszDomainList;
    4713         361 :         oLocalMDMD.SetMetadata(papszMDDup);
    4714         701 :         while (papszIter && *papszIter)
    4715             :         {
    4716         340 :             if (!EQUAL(*papszIter, "") &&
    4717         172 :                 !EQUAL(*papszIter, "IMAGE_STRUCTURE") &&
    4718          15 :                 !EQUAL(*papszIter, "GEOPACKAGE"))
    4719             :             {
    4720           8 :                 oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
    4721             :                                        *papszIter);
    4722             :             }
    4723         340 :             papszIter++;
    4724             :         }
    4725         361 :         if (m_nBandCountFromMetadata > 0)
    4726             :         {
    4727          72 :             oLocalMDMD.SetMetadataItem(
    4728             :                 "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
    4729             :                 "IMAGE_STRUCTURE");
    4730          72 :             if (nBands == 1)
    4731             :             {
    4732          48 :                 const auto poCT = GetRasterBand(1)->GetColorTable();
    4733          48 :                 if (poCT)
    4734             :                 {
    4735          16 :                     std::string osVal("{");
    4736           8 :                     const int nColorCount = poCT->GetColorEntryCount();
    4737        2056 :                     for (int i = 0; i < nColorCount; ++i)
    4738             :                     {
    4739        2048 :                         if (i > 0)
    4740        2040 :                             osVal += ',';
    4741        2048 :                         const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
    4742             :                         osVal +=
    4743        2048 :                             CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
    4744        2048 :                                        psEntry->c2, psEntry->c3, psEntry->c4);
    4745             :                     }
    4746           8 :                     osVal += '}';
    4747           8 :                     oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
    4748             :                                                "IMAGE_STRUCTURE");
    4749             :                 }
    4750             :             }
    4751          72 :             if (nBands == 1)
    4752             :             {
    4753          48 :                 const char *pszTILE_FORMAT = nullptr;
    4754          48 :                 switch (m_eTF)
    4755             :                 {
    4756           0 :                     case GPKG_TF_PNG_JPEG:
    4757           0 :                         pszTILE_FORMAT = "JPEG_PNG";
    4758           0 :                         break;
    4759          42 :                     case GPKG_TF_PNG:
    4760          42 :                         break;
    4761           0 :                     case GPKG_TF_PNG8:
    4762           0 :                         pszTILE_FORMAT = "PNG8";
    4763           0 :                         break;
    4764           3 :                     case GPKG_TF_JPEG:
    4765           3 :                         pszTILE_FORMAT = "JPEG";
    4766           3 :                         break;
    4767           3 :                     case GPKG_TF_WEBP:
    4768           3 :                         pszTILE_FORMAT = "WEBP";
    4769           3 :                         break;
    4770           0 :                     case GPKG_TF_PNG_16BIT:
    4771           0 :                         break;
    4772           0 :                     case GPKG_TF_TIFF_32BIT_FLOAT:
    4773           0 :                         break;
    4774             :                 }
    4775          48 :                 if (pszTILE_FORMAT)
    4776           6 :                     oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
    4777             :                                                "IMAGE_STRUCTURE");
    4778             :             }
    4779             :         }
    4780         503 :         if (GetRasterCount() > 0 &&
    4781         142 :             GetRasterBand(1)->GetRasterDataType() == GDT_Byte)
    4782             :         {
    4783         112 :             int bHasNoData = FALSE;
    4784             :             const double dfNoDataValue =
    4785         112 :                 GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    4786         112 :             if (bHasNoData)
    4787             :             {
    4788           3 :                 oLocalMDMD.SetMetadataItem("NODATA_VALUE",
    4789             :                                            CPLSPrintf("%.17g", dfNoDataValue),
    4790             :                                            "IMAGE_STRUCTURE");
    4791             :             }
    4792             :         }
    4793         608 :         for (int i = 1; i <= GetRasterCount(); ++i)
    4794             :         {
    4795             :             auto poBand =
    4796         247 :                 cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
    4797         247 :             poBand->AddImplicitStatistics(false);
    4798         247 :             char **papszMD = GetRasterBand(i)->GetMetadata();
    4799         247 :             poBand->AddImplicitStatistics(true);
    4800         247 :             if (papszMD)
    4801             :             {
    4802          14 :                 oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
    4803             :             }
    4804             :         }
    4805         361 :         psXMLNode = oLocalMDMD.Serialize();
    4806             :     }
    4807             : 
    4808         361 :     CSLDestroy(papszMDDup);
    4809         361 :     papszMDDup = nullptr;
    4810             : 
    4811         361 :     WriteMetadata(psXMLNode, m_osRasterTable.c_str());
    4812             : 
    4813         361 :     if (!m_osRasterTable.empty())
    4814             :     {
    4815             :         char **papszGeopackageMD =
    4816         142 :             GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
    4817             : 
    4818         142 :         papszMDDup = nullptr;
    4819         151 :         for (char **papszIter = papszGeopackageMD; papszIter && *papszIter;
    4820             :              ++papszIter)
    4821             :         {
    4822           9 :             papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4823             :         }
    4824             : 
    4825         284 :         GDALMultiDomainMetadata oLocalMDMD;
    4826         142 :         oLocalMDMD.SetMetadata(papszMDDup);
    4827         142 :         CSLDestroy(papszMDDup);
    4828         142 :         papszMDDup = nullptr;
    4829         142 :         psXMLNode = oLocalMDMD.Serialize();
    4830             : 
    4831         142 :         WriteMetadata(psXMLNode, nullptr);
    4832             :     }
    4833             : 
    4834         594 :     for (auto &poLayer : m_apoLayers)
    4835             :     {
    4836         233 :         const char *pszIdentifier = poLayer->GetMetadataItem("IDENTIFIER");
    4837         233 :         const char *pszDescription = poLayer->GetMetadataItem("DESCRIPTION");
    4838         233 :         if (pszIdentifier != nullptr)
    4839             :         {
    4840             :             char *pszSQL =
    4841           3 :                 sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
    4842             :                                 "WHERE lower(table_name) = lower('%q')",
    4843             :                                 pszIdentifier, poLayer->GetName());
    4844           3 :             SQLCommand(hDB, pszSQL);
    4845           3 :             sqlite3_free(pszSQL);
    4846             :         }
    4847         233 :         if (pszDescription != nullptr)
    4848             :         {
    4849             :             char *pszSQL =
    4850           3 :                 sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
    4851             :                                 "WHERE lower(table_name) = lower('%q')",
    4852             :                                 pszDescription, poLayer->GetName());
    4853           3 :             SQLCommand(hDB, pszSQL);
    4854           3 :             sqlite3_free(pszSQL);
    4855             :         }
    4856             : 
    4857         233 :         papszMDDup = nullptr;
    4858         617 :         for (char **papszIter = poLayer->GetMetadata(); papszIter && *papszIter;
    4859             :              ++papszIter)
    4860             :         {
    4861         384 :             if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
    4862           3 :                 continue;
    4863         381 :             if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
    4864           3 :                 continue;
    4865         378 :             if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
    4866           0 :                 continue;
    4867         378 :             papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4868             :         }
    4869             : 
    4870             :         {
    4871         233 :             GDALMultiDomainMetadata oLocalMDMD;
    4872         233 :             char **papszDomainList = poLayer->GetMetadataDomainList();
    4873         233 :             char **papszIter = papszDomainList;
    4874         233 :             oLocalMDMD.SetMetadata(papszMDDup);
    4875         501 :             while (papszIter && *papszIter)
    4876             :             {
    4877         268 :                 if (!EQUAL(*papszIter, ""))
    4878          56 :                     oLocalMDMD.SetMetadata(poLayer->GetMetadata(*papszIter),
    4879             :                                            *papszIter);
    4880         268 :                 papszIter++;
    4881             :             }
    4882         233 :             CSLDestroy(papszDomainList);
    4883         233 :             psXMLNode = oLocalMDMD.Serialize();
    4884             :         }
    4885             : 
    4886         233 :         CSLDestroy(papszMDDup);
    4887         233 :         papszMDDup = nullptr;
    4888             : 
    4889         233 :         WriteMetadata(psXMLNode, poLayer->GetName());
    4890             :     }
    4891             : }
    4892             : 
    4893             : /************************************************************************/
    4894             : /*                          GetMetadataItem()                           */
    4895             : /************************************************************************/
    4896             : 
    4897        1526 : const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
    4898             :                                                    const char *pszDomain)
    4899             : {
    4900        1526 :     pszDomain = CheckMetadataDomain(pszDomain);
    4901        1526 :     return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
    4902             : }
    4903             : 
    4904             : /************************************************************************/
    4905             : /*                            SetMetadata()                             */
    4906             : /************************************************************************/
    4907             : 
    4908         146 : CPLErr GDALGeoPackageDataset::SetMetadata(char **papszMetadata,
    4909             :                                           const char *pszDomain)
    4910             : {
    4911         146 :     pszDomain = CheckMetadataDomain(pszDomain);
    4912         146 :     m_bMetadataDirty = true;
    4913         146 :     GetMetadata(); /* force loading from storage if needed */
    4914         146 :     return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
    4915             : }
    4916             : 
    4917             : /************************************************************************/
    4918             : /*                          SetMetadataItem()                           */
    4919             : /************************************************************************/
    4920             : 
    4921          21 : CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
    4922             :                                               const char *pszValue,
    4923             :                                               const char *pszDomain)
    4924             : {
    4925          21 :     pszDomain = CheckMetadataDomain(pszDomain);
    4926          21 :     m_bMetadataDirty = true;
    4927          21 :     GetMetadata(); /* force loading from storage if needed */
    4928          21 :     return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    4929             : }
    4930             : 
    4931             : /************************************************************************/
    4932             : /*                                Create()                              */
    4933             : /************************************************************************/
    4934             : 
    4935         872 : int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
    4936             :                                   int nYSize, int nBandsIn, GDALDataType eDT,
    4937             :                                   char **papszOptions)
    4938             : {
    4939        1744 :     CPLString osCommand;
    4940             : 
    4941             :     /* First, ensure there isn't any such file yet. */
    4942             :     VSIStatBufL sStatBuf;
    4943             : 
    4944         872 :     if (nBandsIn != 0)
    4945             :     {
    4946         223 :         if (eDT == GDT_Byte)
    4947             :         {
    4948         153 :             if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
    4949             :                 nBandsIn != 4)
    4950             :             {
    4951           1 :                 CPLError(CE_Failure, CPLE_NotSupported,
    4952             :                          "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
    4953             :                          "3 (RGB) or 4 (RGBA) band dataset supported for "
    4954             :                          "Byte datatype");
    4955           1 :                 return FALSE;
    4956             :             }
    4957             :         }
    4958          70 :         else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
    4959             :         {
    4960          43 :             if (nBandsIn != 1)
    4961             :             {
    4962           3 :                 CPLError(CE_Failure, CPLE_NotSupported,
    4963             :                          "Only single band dataset supported for non Byte "
    4964             :                          "datatype");
    4965           3 :                 return FALSE;
    4966             :             }
    4967             :         }
    4968             :         else
    4969             :         {
    4970          27 :             CPLError(CE_Failure, CPLE_NotSupported,
    4971             :                      "Only Byte, Int16, UInt16 or Float32 supported");
    4972          27 :             return FALSE;
    4973             :         }
    4974             :     }
    4975             : 
    4976         841 :     const size_t nFilenameLen = strlen(pszFilename);
    4977         841 :     const bool bGpkgZip =
    4978         836 :         (nFilenameLen > strlen(".gpkg.zip") &&
    4979        1677 :          !STARTS_WITH(pszFilename, "/vsizip/") &&
    4980         836 :          EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
    4981             : 
    4982             :     const bool bUseTempFile =
    4983         842 :         bGpkgZip || (CPLTestBool(CPLGetConfigOption(
    4984           1 :                          "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
    4985           1 :                      (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
    4986           1 :                       EQUAL(CPLGetConfigOption(
    4987             :                                 "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
    4988         841 :                             "FORCED")));
    4989             : 
    4990         841 :     bool bFileExists = false;
    4991         841 :     if (VSIStatL(pszFilename, &sStatBuf) == 0)
    4992             :     {
    4993          10 :         bFileExists = true;
    4994          20 :         if (nBandsIn == 0 || bUseTempFile ||
    4995          10 :             !CPLTestBool(
    4996             :                 CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
    4997             :         {
    4998           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4999             :                      "A file system object called '%s' already exists.",
    5000             :                      pszFilename);
    5001             : 
    5002           0 :             return FALSE;
    5003             :         }
    5004             :     }
    5005             : 
    5006         841 :     if (bUseTempFile)
    5007             :     {
    5008           3 :         if (bGpkgZip)
    5009             :         {
    5010           2 :             std::string osFilenameInZip(CPLGetFilename(pszFilename));
    5011           2 :             osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
    5012             :             m_osFinalFilename =
    5013           2 :                 std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
    5014             :         }
    5015             :         else
    5016             :         {
    5017           1 :             m_osFinalFilename = pszFilename;
    5018             :         }
    5019           3 :         m_pszFilename = CPLStrdup(
    5020           6 :             CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename)).c_str());
    5021           3 :         CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
    5022             :     }
    5023             :     else
    5024             :     {
    5025         838 :         m_pszFilename = CPLStrdup(pszFilename);
    5026             :     }
    5027         841 :     m_bNew = true;
    5028         841 :     eAccess = GA_Update;
    5029         841 :     m_bDateTimeWithTZ =
    5030         841 :         EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
    5031             :               "WITH_TZ");
    5032             : 
    5033             :     // for test/debug purposes only. true is the nominal value
    5034         841 :     m_bPNGSupports2Bands =
    5035         841 :         CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
    5036         841 :     m_bPNGSupportsCT =
    5037         841 :         CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
    5038             : 
    5039         841 :     if (!OpenOrCreateDB(bFileExists
    5040             :                             ? SQLITE_OPEN_READWRITE
    5041             :                             : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
    5042           6 :         return FALSE;
    5043             : 
    5044             :     /* Default to synchronous=off for performance for new file */
    5045        1660 :     if (!bFileExists &&
    5046         825 :         CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
    5047             :     {
    5048         327 :         SQLCommand(hDB, "PRAGMA synchronous = OFF");
    5049             :     }
    5050             : 
    5051             :     /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
    5052             :     /* will be written into the main file and supported henceforth */
    5053         835 :     SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
    5054             : 
    5055         835 :     if (bFileExists)
    5056             :     {
    5057          10 :         VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
    5058          10 :         if (fp)
    5059             :         {
    5060             :             GByte abyHeader[100];
    5061          10 :             VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
    5062          10 :             VSIFCloseL(fp);
    5063             : 
    5064          10 :             memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
    5065          10 :             m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
    5066          10 :             memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
    5067          10 :             m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
    5068             : 
    5069          10 :             if (m_nApplicationId == GP10_APPLICATION_ID)
    5070             :             {
    5071           0 :                 CPLDebug("GPKG", "GeoPackage v1.0");
    5072             :             }
    5073          10 :             else if (m_nApplicationId == GP11_APPLICATION_ID)
    5074             :             {
    5075           0 :                 CPLDebug("GPKG", "GeoPackage v1.1");
    5076             :             }
    5077          10 :             else if (m_nApplicationId == GPKG_APPLICATION_ID &&
    5078          10 :                      m_nUserVersion >= GPKG_1_2_VERSION)
    5079             :             {
    5080          10 :                 CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
    5081          10 :                          (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
    5082             :             }
    5083             :         }
    5084             : 
    5085          10 :         DetectSpatialRefSysColumns();
    5086             :     }
    5087             : 
    5088         835 :     const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
    5089         835 :     if (pszVersion && !EQUAL(pszVersion, "AUTO"))
    5090             :     {
    5091          40 :         if (EQUAL(pszVersion, "1.0"))
    5092             :         {
    5093           2 :             m_nApplicationId = GP10_APPLICATION_ID;
    5094           2 :             m_nUserVersion = 0;
    5095             :         }
    5096          38 :         else if (EQUAL(pszVersion, "1.1"))
    5097             :         {
    5098           1 :             m_nApplicationId = GP11_APPLICATION_ID;
    5099           1 :             m_nUserVersion = 0;
    5100             :         }
    5101          37 :         else if (EQUAL(pszVersion, "1.2"))
    5102             :         {
    5103          15 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5104          15 :             m_nUserVersion = GPKG_1_2_VERSION;
    5105             :         }
    5106          22 :         else if (EQUAL(pszVersion, "1.3"))
    5107             :         {
    5108           3 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5109           3 :             m_nUserVersion = GPKG_1_3_VERSION;
    5110             :         }
    5111          19 :         else if (EQUAL(pszVersion, "1.4"))
    5112             :         {
    5113          19 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5114          19 :             m_nUserVersion = GPKG_1_4_VERSION;
    5115             :         }
    5116             :     }
    5117             : 
    5118         835 :     SoftStartTransaction();
    5119             : 
    5120        1670 :     CPLString osSQL;
    5121         835 :     if (!bFileExists)
    5122             :     {
    5123             :         /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
    5124             :          * table */
    5125             :         /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5126             :         osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
    5127             :                 "srs_name TEXT NOT NULL,"
    5128             :                 "srs_id INTEGER NOT NULL PRIMARY KEY,"
    5129             :                 "organization TEXT NOT NULL,"
    5130             :                 "organization_coordsys_id INTEGER NOT NULL,"
    5131             :                 "definition  TEXT NOT NULL,"
    5132         825 :                 "description TEXT";
    5133         825 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
    5134        1003 :                                              "NO")) ||
    5135         178 :             (nBandsIn != 0 && eDT != GDT_Byte))
    5136             :         {
    5137          42 :             m_bHasDefinition12_063 = true;
    5138          42 :             osSQL += ", definition_12_063 TEXT NOT NULL";
    5139          42 :             if (m_nUserVersion >= GPKG_1_4_VERSION)
    5140             :             {
    5141          40 :                 osSQL += ", epoch DOUBLE";
    5142          40 :                 m_bHasEpochColumn = true;
    5143             :             }
    5144             :         }
    5145             :         osSQL += ")"
    5146             :                  ";"
    5147             :                  /* Requirement 11: The gpkg_spatial_ref_sys table in a
    5148             :                     GeoPackage SHALL */
    5149             :                  /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
    5150             :                  /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5151             : 
    5152             :                  "INSERT INTO gpkg_spatial_ref_sys ("
    5153             :                  "srs_name, srs_id, organization, organization_coordsys_id, "
    5154         825 :                  "definition, description";
    5155         825 :         if (m_bHasDefinition12_063)
    5156          42 :             osSQL += ", definition_12_063";
    5157             :         osSQL +=
    5158             :             ") VALUES ("
    5159             :             "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
    5160             :             "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
    5161             :             "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
    5162             :             "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
    5163             :             "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
    5164             :             "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
    5165             :             "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
    5166             :             "', 'longitude/latitude coordinates in decimal degrees on the WGS "
    5167         825 :             "84 spheroid'";
    5168         825 :         if (m_bHasDefinition12_063)
    5169             :             osSQL +=
    5170             :                 ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
    5171             :                 "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
    5172             :                 "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
    5173             :                 "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
    5174             :                 "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
    5175             :                 "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
    5176          42 :                 "ID[\"EPSG\", 4326]]'";
    5177             :         osSQL +=
    5178             :             ")"
    5179             :             ";"
    5180             :             /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
    5181             :                SHALL */
    5182             :             /* contain a record with an srs_id of -1, an organization of “NONE”,
    5183             :              */
    5184             :             /* an organization_coordsys_id of -1, and definition “undefined” */
    5185             :             /* for undefined Cartesian coordinate reference systems */
    5186             :             /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5187             :             "INSERT INTO gpkg_spatial_ref_sys ("
    5188             :             "srs_name, srs_id, organization, organization_coordsys_id, "
    5189         825 :             "definition, description";
    5190         825 :         if (m_bHasDefinition12_063)
    5191          42 :             osSQL += ", definition_12_063";
    5192             :         osSQL += ") VALUES ("
    5193             :                  "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
    5194         825 :                  "'undefined Cartesian coordinate reference system'";
    5195         825 :         if (m_bHasDefinition12_063)
    5196          42 :             osSQL += ", 'undefined'";
    5197             :         osSQL +=
    5198             :             ")"
    5199             :             ";"
    5200             :             /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
    5201             :                SHALL */
    5202             :             /* contain a record with an srs_id of 0, an organization of “NONE”,
    5203             :              */
    5204             :             /* an organization_coordsys_id of 0, and definition “undefined” */
    5205             :             /* for undefined geographic coordinate reference systems */
    5206             :             /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5207             :             "INSERT INTO gpkg_spatial_ref_sys ("
    5208             :             "srs_name, srs_id, organization, organization_coordsys_id, "
    5209         825 :             "definition, description";
    5210         825 :         if (m_bHasDefinition12_063)
    5211          42 :             osSQL += ", definition_12_063";
    5212             :         osSQL += ") VALUES ("
    5213             :                  "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
    5214         825 :                  "'undefined geographic coordinate reference system'";
    5215         825 :         if (m_bHasDefinition12_063)
    5216          42 :             osSQL += ", 'undefined'";
    5217             :         osSQL += ")"
    5218             :                  ";"
    5219             :                  /* Requirement 13: A GeoPackage file SHALL include a
    5220             :                     gpkg_contents table */
    5221             :                  /* http://opengis.github.io/geopackage/#_contents */
    5222             :                  "CREATE TABLE gpkg_contents ("
    5223             :                  "table_name TEXT NOT NULL PRIMARY KEY,"
    5224             :                  "data_type TEXT NOT NULL,"
    5225             :                  "identifier TEXT UNIQUE,"
    5226             :                  "description TEXT DEFAULT '',"
    5227             :                  "last_change DATETIME NOT NULL DEFAULT "
    5228             :                  "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
    5229             :                  "min_x DOUBLE, min_y DOUBLE,"
    5230             :                  "max_x DOUBLE, max_y DOUBLE,"
    5231             :                  "srs_id INTEGER,"
    5232             :                  "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
    5233             :                  "gpkg_spatial_ref_sys(srs_id)"
    5234         825 :                  ")";
    5235             : 
    5236             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    5237         825 :         if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
    5238             :         {
    5239         820 :             m_bHasGPKGOGRContents = true;
    5240             :             osSQL += ";"
    5241             :                      "CREATE TABLE gpkg_ogr_contents("
    5242             :                      "table_name TEXT NOT NULL PRIMARY KEY,"
    5243             :                      "feature_count INTEGER DEFAULT NULL"
    5244         820 :                      ")";
    5245             :         }
    5246             : #endif
    5247             : 
    5248             :         /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
    5249             :          * “features” */
    5250             :         /* data_type SHALL contain a gpkg_geometry_columns table or updateable
    5251             :          * view */
    5252             :         /* http://opengis.github.io/geopackage/#_geometry_columns */
    5253             :         const bool bCreateGeometryColumns =
    5254         825 :             CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
    5255         825 :         if (bCreateGeometryColumns)
    5256             :         {
    5257         824 :             m_bHasGPKGGeometryColumns = true;
    5258         824 :             osSQL += ";";
    5259         824 :             osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
    5260             :         }
    5261             :     }
    5262             : 
    5263             :     const bool bCreateTriggers =
    5264         835 :         CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
    5265          10 :     if ((bFileExists && nBandsIn != 0 &&
    5266          10 :          SQLGetInteger(
    5267             :              hDB,
    5268             :              "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
    5269             :              "AND type in ('table', 'view')",
    5270        1670 :              nullptr) == 0) ||
    5271         834 :         (!bFileExists &&
    5272         825 :          CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
    5273             :     {
    5274         825 :         if (!osSQL.empty())
    5275         824 :             osSQL += ";";
    5276             : 
    5277             :         /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
    5278             :          * Creation SQL  */
    5279             :         osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
    5280             :                  "table_name TEXT NOT NULL PRIMARY KEY,"
    5281             :                  "srs_id INTEGER NOT NULL,"
    5282             :                  "min_x DOUBLE NOT NULL,"
    5283             :                  "min_y DOUBLE NOT NULL,"
    5284             :                  "max_x DOUBLE NOT NULL,"
    5285             :                  "max_y DOUBLE NOT NULL,"
    5286             :                  "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
    5287             :                  "REFERENCES gpkg_contents(table_name),"
    5288             :                  "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
    5289             :                  "gpkg_spatial_ref_sys (srs_id)"
    5290             :                  ")"
    5291             :                  ";"
    5292             : 
    5293             :                  /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
    5294             :                     Creation SQL */
    5295             :                  "CREATE TABLE gpkg_tile_matrix ("
    5296             :                  "table_name TEXT NOT NULL,"
    5297             :                  "zoom_level INTEGER NOT NULL,"
    5298             :                  "matrix_width INTEGER NOT NULL,"
    5299             :                  "matrix_height INTEGER NOT NULL,"
    5300             :                  "tile_width INTEGER NOT NULL,"
    5301             :                  "tile_height INTEGER NOT NULL,"
    5302             :                  "pixel_x_size DOUBLE NOT NULL,"
    5303             :                  "pixel_y_size DOUBLE NOT NULL,"
    5304             :                  "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
    5305             :                  "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
    5306             :                  "REFERENCES gpkg_contents(table_name)"
    5307         825 :                  ")";
    5308             : 
    5309         825 :         if (bCreateTriggers)
    5310             :         {
    5311             :             /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
    5312             :              * Definition SQL */
    5313         825 :             const char *pszTileMatrixTrigger =
    5314             :                 "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
    5315             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5316             :                 "FOR EACH ROW BEGIN "
    5317             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5318             :                 "violates constraint: zoom_level cannot be less than 0') "
    5319             :                 "WHERE (NEW.zoom_level < 0); "
    5320             :                 "END; "
    5321             :                 "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
    5322             :                 "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
    5323             :                 "FOR EACH ROW BEGIN "
    5324             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5325             :                 "violates constraint: zoom_level cannot be less than 0') "
    5326             :                 "WHERE (NEW.zoom_level < 0); "
    5327             :                 "END; "
    5328             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
    5329             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5330             :                 "FOR EACH ROW BEGIN "
    5331             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5332             :                 "violates constraint: matrix_width cannot be less than 1') "
    5333             :                 "WHERE (NEW.matrix_width < 1); "
    5334             :                 "END; "
    5335             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
    5336             :                 "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
    5337             :                 "FOR EACH ROW BEGIN "
    5338             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5339             :                 "violates constraint: matrix_width cannot be less than 1') "
    5340             :                 "WHERE (NEW.matrix_width < 1); "
    5341             :                 "END; "
    5342             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
    5343             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5344             :                 "FOR EACH ROW BEGIN "
    5345             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5346             :                 "violates constraint: matrix_height cannot be less than 1') "
    5347             :                 "WHERE (NEW.matrix_height < 1); "
    5348             :                 "END; "
    5349             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
    5350             :                 "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
    5351             :                 "FOR EACH ROW BEGIN "
    5352             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5353             :                 "violates constraint: matrix_height cannot be less than 1') "
    5354             :                 "WHERE (NEW.matrix_height < 1); "
    5355             :                 "END; "
    5356             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
    5357             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5358             :                 "FOR EACH ROW BEGIN "
    5359             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5360             :                 "violates constraint: pixel_x_size must be greater than 0') "
    5361             :                 "WHERE NOT (NEW.pixel_x_size > 0); "
    5362             :                 "END; "
    5363             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
    5364             :                 "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
    5365             :                 "FOR EACH ROW BEGIN "
    5366             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5367             :                 "violates constraint: pixel_x_size must be greater than 0') "
    5368             :                 "WHERE NOT (NEW.pixel_x_size > 0); "
    5369             :                 "END; "
    5370             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
    5371             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5372             :                 "FOR EACH ROW BEGIN "
    5373             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5374             :                 "violates constraint: pixel_y_size must be greater than 0') "
    5375             :                 "WHERE NOT (NEW.pixel_y_size > 0); "
    5376             :                 "END; "
    5377             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
    5378             :                 "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
    5379             :                 "FOR EACH ROW BEGIN "
    5380             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5381             :                 "violates constraint: pixel_y_size must be greater than 0') "
    5382             :                 "WHERE NOT (NEW.pixel_y_size > 0); "
    5383             :                 "END;";
    5384         825 :             osSQL += ";";
    5385         825 :             osSQL += pszTileMatrixTrigger;
    5386             :         }
    5387             :     }
    5388             : 
    5389         835 :     if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
    5390           1 :         return FALSE;
    5391             : 
    5392         834 :     if (!bFileExists)
    5393             :     {
    5394             :         const char *pszMetadataTables =
    5395         824 :             CSLFetchNameValue(papszOptions, "METADATA_TABLES");
    5396         824 :         if (pszMetadataTables)
    5397           9 :             m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
    5398             : 
    5399         824 :         if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
    5400           0 :             return FALSE;
    5401             : 
    5402         824 :         if (m_bHasDefinition12_063)
    5403             :         {
    5404          84 :             if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
    5405             :                 OGRERR_NONE !=
    5406          42 :                     SQLCommand(hDB, "INSERT INTO gpkg_extensions "
    5407             :                                     "(table_name, column_name, extension_name, "
    5408             :                                     "definition, scope) "
    5409             :                                     "VALUES "
    5410             :                                     "('gpkg_spatial_ref_sys', "
    5411             :                                     "'definition_12_063', 'gpkg_crs_wkt', "
    5412             :                                     "'http://www.geopackage.org/spec120/"
    5413             :                                     "#extension_crs_wkt', 'read-write')"))
    5414             :             {
    5415           0 :                 return FALSE;
    5416             :             }
    5417          42 :             if (m_bHasEpochColumn)
    5418             :             {
    5419          40 :                 if (OGRERR_NONE !=
    5420          40 :                         SQLCommand(
    5421             :                             hDB, "UPDATE gpkg_extensions SET extension_name = "
    5422             :                                  "'gpkg_crs_wkt_1_1' "
    5423          80 :                                  "WHERE extension_name = 'gpkg_crs_wkt'") ||
    5424             :                     OGRERR_NONE !=
    5425          40 :                         SQLCommand(hDB, "INSERT INTO gpkg_extensions "
    5426             :                                         "(table_name, column_name, "
    5427             :                                         "extension_name, definition, scope) "
    5428             :                                         "VALUES "
    5429             :                                         "('gpkg_spatial_ref_sys', 'epoch', "
    5430             :                                         "'gpkg_crs_wkt_1_1', "
    5431             :                                         "'http://www.geopackage.org/spec/"
    5432             :                                         "#extension_crs_wkt', "
    5433             :                                         "'read-write')"))
    5434             :                 {
    5435           0 :                     return FALSE;
    5436             :                 }
    5437             :             }
    5438             :         }
    5439             :     }
    5440             : 
    5441         834 :     if (nBandsIn != 0)
    5442             :     {
    5443         187 :         const std::string osTableName = CPLGetBasenameSafe(m_pszFilename);
    5444             :         m_osRasterTable = CSLFetchNameValueDef(papszOptions, "RASTER_TABLE",
    5445         187 :                                                osTableName.c_str());
    5446         187 :         if (m_osRasterTable.empty())
    5447             :         {
    5448           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5449             :                      "RASTER_TABLE must be set to a non empty value");
    5450           0 :             return FALSE;
    5451             :         }
    5452         187 :         m_bIdentifierAsCO =
    5453         187 :             CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
    5454             :         m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
    5455         187 :                                               m_osRasterTable);
    5456         187 :         m_bDescriptionAsCO =
    5457         187 :             CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
    5458             :         m_osDescription =
    5459         187 :             CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
    5460         187 :         SetDataType(eDT);
    5461         187 :         if (eDT == GDT_Int16)
    5462          16 :             SetGlobalOffsetScale(-32768.0, 1.0);
    5463             : 
    5464             :         /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
    5465             :          * table Create Table SQL (Informative) */
    5466             :         char *pszSQL =
    5467         187 :             sqlite3_mprintf("CREATE TABLE \"%w\" ("
    5468             :                             "id INTEGER PRIMARY KEY AUTOINCREMENT,"
    5469             :                             "zoom_level INTEGER NOT NULL,"
    5470             :                             "tile_column INTEGER NOT NULL,"
    5471             :                             "tile_row INTEGER NOT NULL,"
    5472             :                             "tile_data BLOB NOT NULL,"
    5473             :                             "UNIQUE (zoom_level, tile_column, tile_row)"
    5474             :                             ")",
    5475             :                             m_osRasterTable.c_str());
    5476         187 :         osSQL = pszSQL;
    5477         187 :         sqlite3_free(pszSQL);
    5478             : 
    5479         187 :         if (bCreateTriggers)
    5480             :         {
    5481         187 :             osSQL += ";";
    5482         187 :             osSQL += CreateRasterTriggersSQL(m_osRasterTable);
    5483             :         }
    5484             : 
    5485         187 :         OGRErr eErr = SQLCommand(hDB, osSQL);
    5486         187 :         if (OGRERR_NONE != eErr)
    5487           0 :             return FALSE;
    5488             : 
    5489         187 :         const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
    5490         187 :         if (eDT == GDT_Int16 || eDT == GDT_UInt16)
    5491             :         {
    5492          27 :             m_eTF = GPKG_TF_PNG_16BIT;
    5493          27 :             if (pszTF)
    5494             :             {
    5495           1 :                 if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
    5496             :                 {
    5497           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    5498             :                              "Only AUTO or PNG supported "
    5499             :                              "as tile format for Int16 / UInt16");
    5500             :                 }
    5501             :             }
    5502             :         }
    5503         160 :         else if (eDT == GDT_Float32)
    5504             :         {
    5505          13 :             m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
    5506          13 :             if (pszTF)
    5507             :             {
    5508           5 :                 if (EQUAL(pszTF, "PNG"))
    5509           5 :                     m_eTF = GPKG_TF_PNG_16BIT;
    5510           0 :                 else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
    5511             :                 {
    5512           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    5513             :                              "Only AUTO, PNG or TIFF supported "
    5514             :                              "as tile format for Float32");
    5515             :                 }
    5516             :             }
    5517             :         }
    5518             :         else
    5519             :         {
    5520         147 :             if (pszTF)
    5521             :             {
    5522          71 :                 m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
    5523          71 :                 if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
    5524           7 :                     m_bMetadataDirty = true;
    5525             :             }
    5526          76 :             else if (nBandsIn == 1)
    5527          65 :                 m_eTF = GPKG_TF_PNG;
    5528             :         }
    5529             : 
    5530         187 :         if (eDT != GDT_Byte)
    5531             :         {
    5532          40 :             if (!CreateTileGriddedTable(papszOptions))
    5533           0 :                 return FALSE;
    5534             :         }
    5535             : 
    5536         187 :         nRasterXSize = nXSize;
    5537         187 :         nRasterYSize = nYSize;
    5538             : 
    5539             :         const char *pszTileSize =
    5540         187 :             CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
    5541             :         const char *pszTileWidth =
    5542         187 :             CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
    5543             :         const char *pszTileHeight =
    5544         187 :             CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
    5545         187 :         int nTileWidth = atoi(pszTileWidth);
    5546         187 :         int nTileHeight = atoi(pszTileHeight);
    5547         187 :         if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
    5548         374 :              nTileHeight > 4096) &&
    5549           1 :             !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
    5550             :         {
    5551           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5552             :                      "Invalid block dimensions: %dx%d", nTileWidth,
    5553             :                      nTileHeight);
    5554           0 :             return FALSE;
    5555             :         }
    5556             : 
    5557         507 :         for (int i = 1; i <= nBandsIn; i++)
    5558             :         {
    5559         320 :             SetBand(i, std::make_unique<GDALGeoPackageRasterBand>(
    5560             :                            this, nTileWidth, nTileHeight));
    5561             :         }
    5562             : 
    5563         187 :         GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
    5564             :                                         "IMAGE_STRUCTURE");
    5565         187 :         GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
    5566         187 :         if (!m_osDescription.empty())
    5567           1 :             GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
    5568             : 
    5569         187 :         ParseCompressionOptions(papszOptions);
    5570             : 
    5571         187 :         if (m_eTF == GPKG_TF_WEBP)
    5572             :         {
    5573          10 :             if (!RegisterWebPExtension())
    5574           0 :                 return FALSE;
    5575             :         }
    5576             : 
    5577             :         m_osTilingScheme =
    5578         187 :             CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
    5579         187 :         if (!EQUAL(m_osTilingScheme, "CUSTOM"))
    5580             :         {
    5581          22 :             const auto poTS = GetTilingScheme(m_osTilingScheme);
    5582          22 :             if (!poTS)
    5583           0 :                 return FALSE;
    5584             : 
    5585          43 :             if (nTileWidth != poTS->nTileWidth ||
    5586          21 :                 nTileHeight != poTS->nTileHeight)
    5587             :             {
    5588           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    5589             :                          "Tile dimension should be %dx%d for %s tiling scheme",
    5590           1 :                          poTS->nTileWidth, poTS->nTileHeight,
    5591             :                          m_osTilingScheme.c_str());
    5592           1 :                 return FALSE;
    5593             :             }
    5594             : 
    5595             :             const char *pszZoomLevel =
    5596          21 :                 CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
    5597          21 :             if (pszZoomLevel)
    5598             :             {
    5599           1 :                 m_nZoomLevel = atoi(pszZoomLevel);
    5600           1 :                 int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
    5601           1 :                 while ((1 << nMaxZoomLevelForThisTM) >
    5602           2 :                            INT_MAX / poTS->nTileXCountZoomLevel0 ||
    5603           1 :                        (1 << nMaxZoomLevelForThisTM) >
    5604           1 :                            INT_MAX / poTS->nTileYCountZoomLevel0)
    5605             :                 {
    5606           0 :                     --nMaxZoomLevelForThisTM;
    5607             :                 }
    5608             : 
    5609           1 :                 if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
    5610             :                 {
    5611           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    5612             :                              "ZOOM_LEVEL = %s is invalid. It should be in "
    5613             :                              "[0,%d] range",
    5614             :                              pszZoomLevel, nMaxZoomLevelForThisTM);
    5615           0 :                     return FALSE;
    5616             :                 }
    5617             :             }
    5618             : 
    5619             :             // Implicitly sets SRS.
    5620          21 :             OGRSpatialReference oSRS;
    5621          21 :             if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
    5622           0 :                 return FALSE;
    5623          21 :             char *pszWKT = nullptr;
    5624          21 :             oSRS.exportToWkt(&pszWKT);
    5625          21 :             SetProjection(pszWKT);
    5626          21 :             CPLFree(pszWKT);
    5627             :         }
    5628             :         else
    5629             :         {
    5630         165 :             if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
    5631             :             {
    5632           0 :                 CPLError(
    5633             :                     CE_Failure, CPLE_NotSupported,
    5634             :                     "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
    5635           0 :                 return false;
    5636             :             }
    5637             :         }
    5638             :     }
    5639             : 
    5640         833 :     if (bFileExists && nBandsIn > 0 && eDT == GDT_Byte)
    5641             :     {
    5642             :         // If there was an ogr_empty_table table, we can remove it
    5643           9 :         RemoveOGREmptyTable();
    5644             :     }
    5645             : 
    5646         833 :     SoftCommitTransaction();
    5647             : 
    5648             :     /* Requirement 2 */
    5649             :     /* We have to do this after there's some content so the database file */
    5650             :     /* is not zero length */
    5651         833 :     SetApplicationAndUserVersionId();
    5652             : 
    5653             :     /* Default to synchronous=off for performance for new file */
    5654        1656 :     if (!bFileExists &&
    5655         823 :         CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
    5656             :     {
    5657         327 :         SQLCommand(hDB, "PRAGMA synchronous = OFF");
    5658             :     }
    5659             : 
    5660         833 :     return TRUE;
    5661             : }
    5662             : 
    5663             : /************************************************************************/
    5664             : /*                        RemoveOGREmptyTable()                         */
    5665             : /************************************************************************/
    5666             : 
    5667         646 : void GDALGeoPackageDataset::RemoveOGREmptyTable()
    5668             : {
    5669             :     // Run with sqlite3_exec since we don't want errors to be emitted
    5670         646 :     sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
    5671             :                  nullptr);
    5672         646 :     sqlite3_exec(
    5673             :         hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
    5674             :         nullptr, nullptr, nullptr);
    5675             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    5676         646 :     if (m_bHasGPKGOGRContents)
    5677             :     {
    5678         632 :         sqlite3_exec(hDB,
    5679             :                      "DELETE FROM gpkg_ogr_contents WHERE "
    5680             :                      "table_name = 'ogr_empty_table'",
    5681             :                      nullptr, nullptr, nullptr);
    5682             :     }
    5683             : #endif
    5684         646 :     sqlite3_exec(hDB,
    5685             :                  "DELETE FROM gpkg_geometry_columns WHERE "
    5686             :                  "table_name = 'ogr_empty_table'",
    5687             :                  nullptr, nullptr, nullptr);
    5688         646 : }
    5689             : 
    5690             : /************************************************************************/
    5691             : /*                        CreateTileGriddedTable()                      */
    5692             : /************************************************************************/
    5693             : 
    5694          40 : bool GDALGeoPackageDataset::CreateTileGriddedTable(char **papszOptions)
    5695             : {
    5696          80 :     CPLString osSQL;
    5697          40 :     if (!HasGriddedCoverageAncillaryTable())
    5698             :     {
    5699             :         // It doesn't exist. So create gpkg_extensions table if necessary, and
    5700             :         // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
    5701             :         // and register them as extensions.
    5702          40 :         if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    5703           0 :             return false;
    5704             : 
    5705             :         // Req 1 /table-defs/coverage-ancillary
    5706             :         osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
    5707             :                 "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
    5708             :                 "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
    5709             :                 "datatype TEXT NOT NULL DEFAULT 'integer',"
    5710             :                 "scale REAL NOT NULL DEFAULT 1.0,"
    5711             :                 "offset REAL NOT NULL DEFAULT 0.0,"
    5712             :                 "precision REAL DEFAULT 1.0,"
    5713             :                 "data_null REAL,"
    5714             :                 "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
    5715             :                 "uom TEXT,"
    5716             :                 "field_name TEXT DEFAULT 'Height',"
    5717             :                 "quantity_definition TEXT DEFAULT 'Height',"
    5718             :                 "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
    5719             :                 "REFERENCES gpkg_tile_matrix_set ( table_name ) "
    5720             :                 "CHECK (datatype in ('integer','float')))"
    5721             :                 ";"
    5722             :                 // Requirement 2 /table-defs/tile-ancillary
    5723             :                 "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
    5724             :                 "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
    5725             :                 "tpudt_name TEXT NOT NULL,"
    5726             :                 "tpudt_id INTEGER NOT NULL,"
    5727             :                 "scale REAL NOT NULL DEFAULT 1.0,"
    5728             :                 "offset REAL NOT NULL DEFAULT 0.0,"
    5729             :                 "min REAL DEFAULT NULL,"
    5730             :                 "max REAL DEFAULT NULL,"
    5731             :                 "mean REAL DEFAULT NULL,"
    5732             :                 "std_dev REAL DEFAULT NULL,"
    5733             :                 "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
    5734             :                 "REFERENCES gpkg_contents(table_name),"
    5735             :                 "UNIQUE (tpudt_name, tpudt_id))"
    5736             :                 ";"
    5737             :                 // Requirement 6 /gpkg-extensions
    5738             :                 "INSERT INTO gpkg_extensions "
    5739             :                 "(table_name, column_name, extension_name, definition, scope) "
    5740             :                 "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
    5741             :                 "'gpkg_2d_gridded_coverage', "
    5742             :                 "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5743             :                 "'read-write')"
    5744             :                 ";"
    5745             :                 // Requirement 6 /gpkg-extensions
    5746             :                 "INSERT INTO gpkg_extensions "
    5747             :                 "(table_name, column_name, extension_name, definition, scope) "
    5748             :                 "VALUES ('gpkg_2d_gridded_tile_ancillary', NULL, "
    5749             :                 "'gpkg_2d_gridded_coverage', "
    5750             :                 "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5751             :                 "'read-write')"
    5752          40 :                 ";";
    5753             :     }
    5754             : 
    5755             :     // Requirement 6 /gpkg-extensions
    5756          40 :     char *pszSQL = sqlite3_mprintf(
    5757             :         "INSERT INTO gpkg_extensions "
    5758             :         "(table_name, column_name, extension_name, definition, scope) "
    5759             :         "VALUES ('%q', 'tile_data', "
    5760             :         "'gpkg_2d_gridded_coverage', "
    5761             :         "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5762             :         "'read-write')",
    5763             :         m_osRasterTable.c_str());
    5764          40 :     osSQL += pszSQL;
    5765          40 :     osSQL += ";";
    5766          40 :     sqlite3_free(pszSQL);
    5767             : 
    5768             :     // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
    5769             :     // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
    5770             :     // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
    5771          40 :     m_dfPrecision =
    5772          40 :         CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
    5773             :     CPLString osGridCellEncoding(CSLFetchNameValueDef(
    5774          80 :         papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
    5775          40 :     m_bGridCellEncodingAsCO =
    5776          40 :         CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
    5777          80 :     CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
    5778             :     CPLString osFieldName(
    5779          80 :         CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
    5780             :     CPLString osQuantityDefinition(
    5781          80 :         CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
    5782             : 
    5783         121 :     pszSQL = sqlite3_mprintf(
    5784             :         "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
    5785             :         "(tile_matrix_set_name, datatype, scale, offset, precision, "
    5786             :         "grid_cell_encoding, uom, field_name, quantity_definition) "
    5787             :         "VALUES (%Q, '%s', %.17g, %.17g, %.17g, %Q, %Q, %Q, %Q)",
    5788             :         m_osRasterTable.c_str(),
    5789          40 :         (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
    5790             :         m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
    5791          41 :         osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
    5792             :         osQuantityDefinition.c_str());
    5793          40 :     m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
    5794          40 :     sqlite3_free(pszSQL);
    5795             : 
    5796             :     // Requirement 3 /gpkg-spatial-ref-sys-row
    5797             :     auto oResultTable = SQLQuery(
    5798          80 :         hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
    5799          40 :     bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
    5800          40 :     if (!bHasEPSG4979)
    5801             :     {
    5802          41 :         if (!m_bHasDefinition12_063 &&
    5803           1 :             !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
    5804             :         {
    5805           0 :             return false;
    5806             :         }
    5807             : 
    5808             :         // This is WKT 2...
    5809          40 :         const char *pszWKT =
    5810             :             "GEODCRS[\"WGS 84\","
    5811             :             "DATUM[\"World Geodetic System 1984\","
    5812             :             "  ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
    5813             :             "LENGTHUNIT[\"metre\",1.0]]],"
    5814             :             "CS[ellipsoidal,3],"
    5815             :             "  AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
    5816             :             "0.0174532925199433]],"
    5817             :             "  AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
    5818             :             "0.0174532925199433]],"
    5819             :             "  AXIS[\"ellipsoidal height\",up,ORDER[3],"
    5820             :             "LENGTHUNIT[\"metre\",1.0]],"
    5821             :             "ID[\"EPSG\",4979]]";
    5822             : 
    5823          40 :         pszSQL = sqlite3_mprintf(
    5824             :             "INSERT INTO gpkg_spatial_ref_sys "
    5825             :             "(srs_name,srs_id,organization,organization_coordsys_id,"
    5826             :             "definition,definition_12_063) VALUES "
    5827             :             "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
    5828             :             pszWKT);
    5829          40 :         osSQL += ";";
    5830          40 :         osSQL += pszSQL;
    5831          40 :         sqlite3_free(pszSQL);
    5832             :     }
    5833             : 
    5834          40 :     return SQLCommand(hDB, osSQL) == OGRERR_NONE;
    5835             : }
    5836             : 
    5837             : /************************************************************************/
    5838             : /*                    HasGriddedCoverageAncillaryTable()                */
    5839             : /************************************************************************/
    5840             : 
    5841          44 : bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
    5842             : {
    5843             :     auto oResultTable = SQLQuery(
    5844             :         hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
    5845          44 :              "name = 'gpkg_2d_gridded_coverage_ancillary'");
    5846          44 :     bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
    5847          88 :     return bHasTable;
    5848             : }
    5849             : 
    5850             : /************************************************************************/
    5851             : /*                      GetUnderlyingDataset()                          */
    5852             : /************************************************************************/
    5853             : 
    5854           3 : static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
    5855             : {
    5856           3 :     if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
    5857             :     {
    5858           0 :         auto poTmpDS = poVRTDS->GetSingleSimpleSource();
    5859           0 :         if (poTmpDS)
    5860           0 :             return poTmpDS;
    5861             :     }
    5862             : 
    5863           3 :     return poSrcDS;
    5864             : }
    5865             : 
    5866             : /************************************************************************/
    5867             : /*                            CreateCopy()                              */
    5868             : /************************************************************************/
    5869             : 
    5870             : typedef struct
    5871             : {
    5872             :     const char *pszName;
    5873             :     GDALResampleAlg eResampleAlg;
    5874             : } WarpResamplingAlg;
    5875             : 
    5876             : static const WarpResamplingAlg asResamplingAlg[] = {
    5877             :     {"NEAREST", GRA_NearestNeighbour},
    5878             :     {"BILINEAR", GRA_Bilinear},
    5879             :     {"CUBIC", GRA_Cubic},
    5880             :     {"CUBICSPLINE", GRA_CubicSpline},
    5881             :     {"LANCZOS", GRA_Lanczos},
    5882             :     {"MODE", GRA_Mode},
    5883             :     {"AVERAGE", GRA_Average},
    5884             :     {"RMS", GRA_RMS},
    5885             : };
    5886             : 
    5887         160 : GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
    5888             :                                                GDALDataset *poSrcDS,
    5889             :                                                int bStrict, char **papszOptions,
    5890             :                                                GDALProgressFunc pfnProgress,
    5891             :                                                void *pProgressData)
    5892             : {
    5893         160 :     const int nBands = poSrcDS->GetRasterCount();
    5894         160 :     if (nBands == 0)
    5895             :     {
    5896           2 :         GDALDataset *poDS = nullptr;
    5897             :         GDALDriver *poThisDriver =
    5898           2 :             GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
    5899           2 :         if (poThisDriver != nullptr)
    5900             :         {
    5901           2 :             poDS = poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS,
    5902             :                                                    bStrict, papszOptions,
    5903             :                                                    pfnProgress, pProgressData);
    5904             :         }
    5905           2 :         return poDS;
    5906             :     }
    5907             : 
    5908             :     const char *pszTilingScheme =
    5909         158 :         CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
    5910             : 
    5911         316 :     CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
    5912         158 :     if (CPLTestBool(
    5913         164 :             CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
    5914           6 :         CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
    5915             :     {
    5916             :         const std::string osBasename(CPLGetBasenameSafe(
    5917           6 :             GetUnderlyingDataset(poSrcDS)->GetDescription()));
    5918           3 :         apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename.c_str());
    5919             :     }
    5920             : 
    5921         158 :     if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
    5922             :     {
    5923           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    5924             :                  "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
    5925             :                  "4 (RGBA) band dataset supported");
    5926           1 :         return nullptr;
    5927             :     }
    5928             : 
    5929         157 :     const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
    5930         314 :     if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
    5931         157 :         !EQUAL(pszUnitType, ""))
    5932             :     {
    5933           1 :         apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
    5934             :     }
    5935             : 
    5936         157 :     if (EQUAL(pszTilingScheme, "CUSTOM"))
    5937             :     {
    5938         133 :         if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
    5939             :         {
    5940           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    5941             :                      "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
    5942           0 :             return nullptr;
    5943             :         }
    5944             : 
    5945         133 :         GDALGeoPackageDataset *poDS = nullptr;
    5946             :         GDALDriver *poThisDriver =
    5947         133 :             GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
    5948         133 :         if (poThisDriver != nullptr)
    5949             :         {
    5950         133 :             apszUpdatedOptions.SetNameValue("SKIP_HOLES", "YES");
    5951         133 :             poDS = cpl::down_cast<GDALGeoPackageDataset *>(
    5952             :                 poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
    5953             :                                                 apszUpdatedOptions, pfnProgress,
    5954         133 :                                                 pProgressData));
    5955             : 
    5956         246 :             if (poDS != nullptr &&
    5957         133 :                 poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_Byte &&
    5958             :                 nBands <= 3)
    5959             :             {
    5960          73 :                 poDS->m_nBandCountFromMetadata = nBands;
    5961          73 :                 poDS->m_bMetadataDirty = true;
    5962             :             }
    5963             :         }
    5964         133 :         if (poDS)
    5965         113 :             poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    5966         133 :         return poDS;
    5967             :     }
    5968             : 
    5969          48 :     const auto poTS = GetTilingScheme(pszTilingScheme);
    5970          24 :     if (!poTS)
    5971             :     {
    5972           2 :         return nullptr;
    5973             :     }
    5974          22 :     const int nEPSGCode = poTS->nEPSGCode;
    5975             : 
    5976          44 :     OGRSpatialReference oSRS;
    5977          22 :     if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
    5978             :     {
    5979           0 :         return nullptr;
    5980             :     }
    5981          22 :     char *pszWKT = nullptr;
    5982          22 :     oSRS.exportToWkt(&pszWKT);
    5983          22 :     char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
    5984             : 
    5985          22 :     void *hTransformArg = nullptr;
    5986             : 
    5987             :     // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
    5988             :     // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
    5989             :     // EPSG:3857.
    5990             :     double adfSrcGeoTransform[6];
    5991          22 :     std::unique_ptr<GDALDataset> poTmpDS;
    5992          22 :     bool bEPSG3857Adjust = false;
    5993          30 :     if (nEPSGCode == 3857 &&
    5994           8 :         poSrcDS->GetGeoTransform(adfSrcGeoTransform) == CE_None &&
    5995          38 :         adfSrcGeoTransform[2] == 0 && adfSrcGeoTransform[4] == 0 &&
    5996           8 :         adfSrcGeoTransform[5] < 0)
    5997             :     {
    5998           8 :         const auto poSrcSRS = poSrcDS->GetSpatialRef();
    5999           8 :         if (poSrcSRS && poSrcSRS->IsGeographic())
    6000             :         {
    6001           2 :             double maxLat = adfSrcGeoTransform[3];
    6002           2 :             double minLat = adfSrcGeoTransform[3] +
    6003           2 :                             poSrcDS->GetRasterYSize() * adfSrcGeoTransform[5];
    6004             :             // Corresponds to the latitude of below MAX_GM
    6005           2 :             constexpr double MAX_LAT = 85.0511287798066;
    6006           2 :             bool bModified = false;
    6007           2 :             if (maxLat > MAX_LAT)
    6008             :             {
    6009           2 :                 maxLat = MAX_LAT;
    6010           2 :                 bModified = true;
    6011             :             }
    6012           2 :             if (minLat < -MAX_LAT)
    6013             :             {
    6014           2 :                 minLat = -MAX_LAT;
    6015           2 :                 bModified = true;
    6016             :             }
    6017           2 :             if (bModified)
    6018             :             {
    6019           4 :                 CPLStringList aosOptions;
    6020           2 :                 aosOptions.AddString("-of");
    6021           2 :                 aosOptions.AddString("VRT");
    6022           2 :                 aosOptions.AddString("-projwin");
    6023             :                 aosOptions.AddString(
    6024           2 :                     CPLSPrintf("%.17g", adfSrcGeoTransform[0]));
    6025           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
    6026             :                 aosOptions.AddString(
    6027           2 :                     CPLSPrintf("%.17g", adfSrcGeoTransform[0] +
    6028           2 :                                             poSrcDS->GetRasterXSize() *
    6029           2 :                                                 adfSrcGeoTransform[1]));
    6030           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", minLat));
    6031             :                 auto psOptions =
    6032           2 :                     GDALTranslateOptionsNew(aosOptions.List(), nullptr);
    6033           2 :                 poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
    6034             :                     "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
    6035           2 :                 GDALTranslateOptionsFree(psOptions);
    6036           2 :                 if (poTmpDS)
    6037             :                 {
    6038           2 :                     bEPSG3857Adjust = true;
    6039           2 :                     hTransformArg = GDALCreateGenImgProjTransformer2(
    6040           2 :                         GDALDataset::FromHandle(poTmpDS.get()), nullptr,
    6041             :                         papszTO);
    6042             :                 }
    6043             :             }
    6044             :         }
    6045             :     }
    6046          22 :     if (hTransformArg == nullptr)
    6047             :     {
    6048             :         hTransformArg =
    6049          20 :             GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
    6050             :     }
    6051             : 
    6052          22 :     if (hTransformArg == nullptr)
    6053             :     {
    6054           1 :         CPLFree(pszWKT);
    6055           1 :         CSLDestroy(papszTO);
    6056           1 :         return nullptr;
    6057             :     }
    6058             : 
    6059          21 :     GDALTransformerInfo *psInfo =
    6060             :         static_cast<GDALTransformerInfo *>(hTransformArg);
    6061             :     double adfGeoTransform[6];
    6062             :     double adfExtent[4];
    6063             :     int nXSize, nYSize;
    6064             : 
    6065          21 :     if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
    6066             :                                  adfGeoTransform, &nXSize, &nYSize, adfExtent,
    6067          21 :                                  0) != CE_None)
    6068             :     {
    6069           0 :         CPLFree(pszWKT);
    6070           0 :         CSLDestroy(papszTO);
    6071           0 :         GDALDestroyGenImgProjTransformer(hTransformArg);
    6072           0 :         return nullptr;
    6073             :     }
    6074             : 
    6075          21 :     GDALDestroyGenImgProjTransformer(hTransformArg);
    6076          21 :     hTransformArg = nullptr;
    6077          21 :     poTmpDS.reset();
    6078             : 
    6079          21 :     if (bEPSG3857Adjust)
    6080             :     {
    6081           2 :         constexpr double SPHERICAL_RADIUS = 6378137.0;
    6082           2 :         constexpr double MAX_GM =
    6083             :             SPHERICAL_RADIUS * M_PI;  // 20037508.342789244
    6084           2 :         double maxNorthing = adfGeoTransform[3];
    6085           2 :         double minNorthing = adfGeoTransform[3] + adfGeoTransform[5] * nYSize;
    6086           2 :         bool bChanged = false;
    6087           2 :         if (maxNorthing > MAX_GM)
    6088             :         {
    6089           2 :             bChanged = true;
    6090           2 :             maxNorthing = MAX_GM;
    6091             :         }
    6092           2 :         if (minNorthing < -MAX_GM)
    6093             :         {
    6094           2 :             bChanged = true;
    6095           2 :             minNorthing = -MAX_GM;
    6096             :         }
    6097           2 :         if (bChanged)
    6098             :         {
    6099           2 :             adfGeoTransform[3] = maxNorthing;
    6100           2 :             nYSize =
    6101           2 :                 int((maxNorthing - minNorthing) / (-adfGeoTransform[5]) + 0.5);
    6102           2 :             adfExtent[1] = maxNorthing + nYSize * adfGeoTransform[5];
    6103           2 :             adfExtent[3] = maxNorthing;
    6104             :         }
    6105             :     }
    6106             : 
    6107          21 :     double dfComputedRes = adfGeoTransform[1];
    6108          21 :     double dfPrevRes = 0.0;
    6109          21 :     double dfRes = 0.0;
    6110          21 :     int nZoomLevel = 0;  // Used after for.
    6111          21 :     const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
    6112          21 :     if (pszZoomLevel)
    6113             :     {
    6114           2 :         nZoomLevel = atoi(pszZoomLevel);
    6115             : 
    6116           2 :         int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
    6117           2 :         while ((1 << nMaxZoomLevelForThisTM) >
    6118           4 :                    INT_MAX / poTS->nTileXCountZoomLevel0 ||
    6119           2 :                (1 << nMaxZoomLevelForThisTM) >
    6120           2 :                    INT_MAX / poTS->nTileYCountZoomLevel0)
    6121             :         {
    6122           0 :             --nMaxZoomLevelForThisTM;
    6123             :         }
    6124             : 
    6125           2 :         if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
    6126             :         {
    6127           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6128             :                      "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
    6129             :                      pszZoomLevel, nMaxZoomLevelForThisTM);
    6130           1 :             CPLFree(pszWKT);
    6131           1 :             CSLDestroy(papszTO);
    6132           1 :             return nullptr;
    6133             :         }
    6134             :     }
    6135             :     else
    6136             :     {
    6137         171 :         for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
    6138             :         {
    6139         171 :             dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
    6140         171 :             if (dfComputedRes > dfRes ||
    6141         152 :                 fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
    6142             :                 break;
    6143         152 :             dfPrevRes = dfRes;
    6144             :         }
    6145          38 :         if (nZoomLevel == MAX_ZOOM_LEVEL ||
    6146          38 :             (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
    6147          19 :             (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
    6148             :         {
    6149           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6150             :                      "Could not find an appropriate zoom level");
    6151           0 :             CPLFree(pszWKT);
    6152           0 :             CSLDestroy(papszTO);
    6153           0 :             return nullptr;
    6154             :         }
    6155             : 
    6156          19 :         if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
    6157             :         {
    6158          17 :             const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
    6159             :                 papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
    6160          17 :             if (EQUAL(pszZoomLevelStrategy, "LOWER"))
    6161             :             {
    6162           1 :                 nZoomLevel--;
    6163             :             }
    6164          16 :             else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
    6165             :             {
    6166             :                 /* do nothing */
    6167             :             }
    6168             :             else
    6169             :             {
    6170          15 :                 if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
    6171          13 :                     nZoomLevel--;
    6172             :             }
    6173             :         }
    6174             :     }
    6175             : 
    6176          20 :     dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
    6177             : 
    6178          20 :     double dfMinX = adfExtent[0];
    6179          20 :     double dfMinY = adfExtent[1];
    6180          20 :     double dfMaxX = adfExtent[2];
    6181          20 :     double dfMaxY = adfExtent[3];
    6182             : 
    6183          20 :     nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
    6184          20 :     nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
    6185          20 :     adfGeoTransform[1] = dfRes;
    6186          20 :     adfGeoTransform[5] = -dfRes;
    6187             : 
    6188          20 :     const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
    6189          20 :     int nTargetBands = nBands;
    6190             :     /* For grey level or RGB, if there's reprojection involved, add an alpha */
    6191             :     /* channel */
    6192          37 :     if (eDT == GDT_Byte &&
    6193          13 :         ((nBands == 1 &&
    6194          17 :           poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
    6195             :          nBands == 3))
    6196             :     {
    6197          30 :         OGRSpatialReference oSrcSRS;
    6198          15 :         oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
    6199          15 :         oSrcSRS.AutoIdentifyEPSG();
    6200          30 :         if (oSrcSRS.GetAuthorityCode(nullptr) == nullptr ||
    6201          15 :             atoi(oSrcSRS.GetAuthorityCode(nullptr)) != nEPSGCode)
    6202             :         {
    6203          13 :             nTargetBands++;
    6204             :         }
    6205             :     }
    6206             : 
    6207          20 :     GDALResampleAlg eResampleAlg = GRA_Bilinear;
    6208          20 :     const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
    6209          20 :     if (pszResampling)
    6210             :     {
    6211           6 :         for (size_t iAlg = 0;
    6212           6 :              iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
    6213             :              iAlg++)
    6214             :         {
    6215           6 :             if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
    6216             :             {
    6217           3 :                 eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
    6218           3 :                 break;
    6219             :             }
    6220             :         }
    6221             :     }
    6222             : 
    6223          16 :     if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
    6224          36 :         eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
    6225             :     {
    6226           0 :         CPLError(
    6227             :             CE_Warning, CPLE_AppDefined,
    6228             :             "Input dataset has a color table, which will likely lead to "
    6229             :             "bad results when using a resampling method other than "
    6230             :             "nearest neighbour or mode. Converting the dataset to 24/32 bit "
    6231             :             "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
    6232             :     }
    6233             : 
    6234          40 :     auto poDS = std::make_unique<GDALGeoPackageDataset>();
    6235          20 :     if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
    6236             :                        apszUpdatedOptions)))
    6237             :     {
    6238           1 :         CPLFree(pszWKT);
    6239           1 :         CSLDestroy(papszTO);
    6240           1 :         return nullptr;
    6241             :     }
    6242             : 
    6243             :     // Assign nodata values before the SetGeoTransform call.
    6244             :     // SetGeoTransform will trigger creation of the overview datasets for each
    6245             :     // zoom level and at that point the nodata value needs to be known.
    6246          19 :     int bHasNoData = FALSE;
    6247             :     double dfNoDataValue =
    6248          19 :         poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    6249          19 :     if (eDT != GDT_Byte && bHasNoData)
    6250             :     {
    6251           3 :         poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
    6252             :     }
    6253             : 
    6254          19 :     poDS->SetGeoTransform(adfGeoTransform);
    6255          19 :     poDS->SetProjection(pszWKT);
    6256          19 :     CPLFree(pszWKT);
    6257          19 :     pszWKT = nullptr;
    6258          24 :     if (nTargetBands == 1 && nBands == 1 &&
    6259           5 :         poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
    6260             :     {
    6261           2 :         poDS->GetRasterBand(1)->SetColorTable(
    6262           1 :             poSrcDS->GetRasterBand(1)->GetColorTable());
    6263             :     }
    6264             : 
    6265             :     hTransformArg =
    6266          19 :         GDALCreateGenImgProjTransformer2(poSrcDS, poDS.get(), papszTO);
    6267          19 :     CSLDestroy(papszTO);
    6268          19 :     if (hTransformArg == nullptr)
    6269             :     {
    6270           0 :         return nullptr;
    6271             :     }
    6272             : 
    6273          19 :     poDS->SetMetadata(poSrcDS->GetMetadata());
    6274             : 
    6275             :     /* -------------------------------------------------------------------- */
    6276             :     /*      Warp the transformer with a linear approximator                 */
    6277             :     /* -------------------------------------------------------------------- */
    6278          19 :     hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
    6279             :                                                 hTransformArg, 0.125);
    6280          19 :     GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
    6281             : 
    6282             :     /* -------------------------------------------------------------------- */
    6283             :     /*      Setup warp options.                                             */
    6284             :     /* -------------------------------------------------------------------- */
    6285          19 :     GDALWarpOptions *psWO = GDALCreateWarpOptions();
    6286             : 
    6287          19 :     psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
    6288          19 :     psWO->papszWarpOptions =
    6289          19 :         CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
    6290          19 :     if (bHasNoData)
    6291             :     {
    6292           3 :         if (dfNoDataValue == 0.0)
    6293             :         {
    6294             :             // Do not initialize in the case where nodata != 0, since we
    6295             :             // want the GeoPackage driver to return empty tiles at the nodata
    6296             :             // value instead of 0 as GDAL core would
    6297           0 :             psWO->papszWarpOptions =
    6298           0 :                 CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
    6299             :         }
    6300             : 
    6301           3 :         psWO->padfSrcNoDataReal =
    6302           3 :             static_cast<double *>(CPLMalloc(sizeof(double)));
    6303           3 :         psWO->padfSrcNoDataReal[0] = dfNoDataValue;
    6304             : 
    6305           3 :         psWO->padfDstNoDataReal =
    6306           3 :             static_cast<double *>(CPLMalloc(sizeof(double)));
    6307           3 :         psWO->padfDstNoDataReal[0] = dfNoDataValue;
    6308             :     }
    6309          19 :     psWO->eWorkingDataType = eDT;
    6310          19 :     psWO->eResampleAlg = eResampleAlg;
    6311             : 
    6312          19 :     psWO->hSrcDS = poSrcDS;
    6313          19 :     psWO->hDstDS = poDS.get();
    6314             : 
    6315          19 :     psWO->pfnTransformer = GDALApproxTransform;
    6316          19 :     psWO->pTransformerArg = hTransformArg;
    6317             : 
    6318          19 :     psWO->pfnProgress = pfnProgress;
    6319          19 :     psWO->pProgressArg = pProgressData;
    6320             : 
    6321             :     /* -------------------------------------------------------------------- */
    6322             :     /*      Setup band mapping.                                             */
    6323             :     /* -------------------------------------------------------------------- */
    6324             : 
    6325          19 :     if (nBands == 2 || nBands == 4)
    6326           1 :         psWO->nBandCount = nBands - 1;
    6327             :     else
    6328          18 :         psWO->nBandCount = nBands;
    6329             : 
    6330          19 :     psWO->panSrcBands =
    6331          19 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    6332          19 :     psWO->panDstBands =
    6333          19 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    6334             : 
    6335          46 :     for (int i = 0; i < psWO->nBandCount; i++)
    6336             :     {
    6337          27 :         psWO->panSrcBands[i] = i + 1;
    6338          27 :         psWO->panDstBands[i] = i + 1;
    6339             :     }
    6340             : 
    6341          19 :     if (nBands == 2 || nBands == 4)
    6342             :     {
    6343           1 :         psWO->nSrcAlphaBand = nBands;
    6344             :     }
    6345          19 :     if (nTargetBands == 2 || nTargetBands == 4)
    6346             :     {
    6347          13 :         psWO->nDstAlphaBand = nTargetBands;
    6348             :     }
    6349             : 
    6350             :     /* -------------------------------------------------------------------- */
    6351             :     /*      Initialize and execute the warp.                                */
    6352             :     /* -------------------------------------------------------------------- */
    6353          38 :     GDALWarpOperation oWO;
    6354             : 
    6355          19 :     CPLErr eErr = oWO.Initialize(psWO);
    6356          19 :     if (eErr == CE_None)
    6357             :     {
    6358             :         /*if( bMulti )
    6359             :             eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
    6360             :         else*/
    6361          19 :         eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
    6362             :     }
    6363          19 :     if (eErr != CE_None)
    6364             :     {
    6365           0 :         poDS.reset();
    6366             :     }
    6367             : 
    6368          19 :     GDALDestroyTransformer(hTransformArg);
    6369          19 :     GDALDestroyWarpOptions(psWO);
    6370             : 
    6371          19 :     if (poDS)
    6372          19 :         poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    6373             : 
    6374          19 :     return poDS.release();
    6375             : }
    6376             : 
    6377             : /************************************************************************/
    6378             : /*                        ParseCompressionOptions()                     */
    6379             : /************************************************************************/
    6380             : 
    6381         456 : void GDALGeoPackageDataset::ParseCompressionOptions(char **papszOptions)
    6382             : {
    6383         456 :     const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
    6384         456 :     if (pszZLevel)
    6385           0 :         m_nZLevel = atoi(pszZLevel);
    6386             : 
    6387         456 :     const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
    6388         456 :     if (pszQuality)
    6389           0 :         m_nQuality = atoi(pszQuality);
    6390             : 
    6391         456 :     const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
    6392         456 :     if (pszDither)
    6393           0 :         m_bDither = CPLTestBool(pszDither);
    6394         456 : }
    6395             : 
    6396             : /************************************************************************/
    6397             : /*                          RegisterWebPExtension()                     */
    6398             : /************************************************************************/
    6399             : 
    6400          11 : bool GDALGeoPackageDataset::RegisterWebPExtension()
    6401             : {
    6402          11 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    6403           0 :         return false;
    6404             : 
    6405          11 :     char *pszSQL = sqlite3_mprintf(
    6406             :         "INSERT INTO gpkg_extensions "
    6407             :         "(table_name, column_name, extension_name, definition, scope) "
    6408             :         "VALUES "
    6409             :         "('%q', 'tile_data', 'gpkg_webp', "
    6410             :         "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
    6411             :         "'read-write')",
    6412             :         m_osRasterTable.c_str());
    6413          11 :     const OGRErr eErr = SQLCommand(hDB, pszSQL);
    6414          11 :     sqlite3_free(pszSQL);
    6415             : 
    6416          11 :     return OGRERR_NONE == eErr;
    6417             : }
    6418             : 
    6419             : /************************************************************************/
    6420             : /*                       RegisterZoomOtherExtension()                   */
    6421             : /************************************************************************/
    6422             : 
    6423           1 : bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
    6424             : {
    6425           1 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    6426           0 :         return false;
    6427             : 
    6428           1 :     char *pszSQL = sqlite3_mprintf(
    6429             :         "INSERT INTO gpkg_extensions "
    6430             :         "(table_name, column_name, extension_name, definition, scope) "
    6431             :         "VALUES "
    6432             :         "('%q', 'tile_data', 'gpkg_zoom_other', "
    6433             :         "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
    6434             :         "'read-write')",
    6435             :         m_osRasterTable.c_str());
    6436           1 :     const OGRErr eErr = SQLCommand(hDB, pszSQL);
    6437           1 :     sqlite3_free(pszSQL);
    6438           1 :     return OGRERR_NONE == eErr;
    6439             : }
    6440             : 
    6441             : /************************************************************************/
    6442             : /*                              GetLayer()                              */
    6443             : /************************************************************************/
    6444             : 
    6445       15268 : OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer)
    6446             : 
    6447             : {
    6448       15268 :     if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
    6449           6 :         return nullptr;
    6450             :     else
    6451       15262 :         return m_apoLayers[iLayer].get();
    6452             : }
    6453             : 
    6454             : /************************************************************************/
    6455             : /*                           LaunderName()                              */
    6456             : /************************************************************************/
    6457             : 
    6458             : /** Launder identifiers (table, column names) according to guidance at
    6459             :  * https://www.geopackage.org/guidance/getting-started.html:
    6460             :  * "For maximum interoperability, start your database identifiers (table names,
    6461             :  * column names, etc.) with a lowercase character and only use lowercase
    6462             :  * characters, numbers 0-9, and underscores (_)."
    6463             :  */
    6464             : 
    6465             : /* static */
    6466           5 : std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
    6467             : {
    6468           5 :     char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
    6469          10 :     const std::string osStrASCII(pszASCII);
    6470           5 :     CPLFree(pszASCII);
    6471             : 
    6472          10 :     std::string osRet;
    6473           5 :     osRet.reserve(osStrASCII.size());
    6474             : 
    6475          29 :     for (size_t i = 0; i < osStrASCII.size(); ++i)
    6476             :     {
    6477          24 :         if (osRet.empty())
    6478             :         {
    6479           5 :             if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
    6480             :             {
    6481           2 :                 osRet += (osStrASCII[i] - 'A' + 'a');
    6482             :             }
    6483           3 :             else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
    6484             :             {
    6485           2 :                 osRet += osStrASCII[i];
    6486             :             }
    6487             :             else
    6488             :             {
    6489           1 :                 continue;
    6490             :             }
    6491             :         }
    6492          19 :         else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
    6493             :         {
    6494          11 :             osRet += (osStrASCII[i] - 'A' + 'a');
    6495             :         }
    6496           9 :         else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
    6497          14 :                  (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
    6498           5 :                  osStrASCII[i] == '_')
    6499             :         {
    6500           7 :             osRet += osStrASCII[i];
    6501             :         }
    6502             :         else
    6503             :         {
    6504           1 :             osRet += '_';
    6505             :         }
    6506             :     }
    6507             : 
    6508           5 :     if (osRet.empty() && !osStrASCII.empty())
    6509           2 :         return LaunderName(std::string("x").append(osStrASCII));
    6510             : 
    6511           4 :     if (osRet != osStr)
    6512             :     {
    6513           3 :         CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
    6514             :                  osRet.c_str());
    6515             :     }
    6516             : 
    6517           4 :     return osRet;
    6518             : }
    6519             : 
    6520             : /************************************************************************/
    6521             : /*                          ICreateLayer()                              */
    6522             : /************************************************************************/
    6523             : 
    6524             : OGRLayer *
    6525         736 : GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
    6526             :                                     const OGRGeomFieldDefn *poSrcGeomFieldDefn,
    6527             :                                     CSLConstList papszOptions)
    6528             : {
    6529             :     /* -------------------------------------------------------------------- */
    6530             :     /*      Verify we are in update mode.                                   */
    6531             :     /* -------------------------------------------------------------------- */
    6532         736 :     if (!GetUpdate())
    6533             :     {
    6534           0 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
    6535             :                  "Data source %s opened read-only.\n"
    6536             :                  "New layer %s cannot be created.\n",
    6537             :                  m_pszFilename, pszLayerName);
    6538             : 
    6539           0 :         return nullptr;
    6540             :     }
    6541             : 
    6542             :     const bool bLaunder =
    6543         736 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
    6544             :     const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
    6545        2208 :                                            : std::string(pszLayerName));
    6546             : 
    6547             :     const auto eGType =
    6548         736 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
    6549             :     const auto poSpatialRef =
    6550         736 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
    6551             : 
    6552         736 :     if (!m_bHasGPKGGeometryColumns)
    6553             :     {
    6554           1 :         if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
    6555             :         {
    6556           0 :             return nullptr;
    6557             :         }
    6558           1 :         m_bHasGPKGGeometryColumns = true;
    6559             :     }
    6560             : 
    6561             :     // Check identifier unicity
    6562         736 :     const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
    6563         736 :     if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
    6564           0 :         pszIdentifier = nullptr;
    6565         736 :     if (pszIdentifier != nullptr)
    6566             :     {
    6567          13 :         for (auto &poLayer : m_apoLayers)
    6568             :         {
    6569             :             const char *pszOtherIdentifier =
    6570           9 :                 poLayer->GetMetadataItem("IDENTIFIER");
    6571           9 :             if (pszOtherIdentifier == nullptr)
    6572           6 :                 pszOtherIdentifier = poLayer->GetName();
    6573          18 :             if (pszOtherIdentifier != nullptr &&
    6574          12 :                 EQUAL(pszOtherIdentifier, pszIdentifier) &&
    6575           3 :                 !EQUAL(poLayer->GetName(), osTableName.c_str()))
    6576             :             {
    6577           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    6578             :                          "Identifier %s is already used by table %s",
    6579             :                          pszIdentifier, poLayer->GetName());
    6580           2 :                 return nullptr;
    6581             :             }
    6582             :         }
    6583             : 
    6584             :         // In case there would be table in gpkg_contents not listed as a
    6585             :         // vector layer
    6586           4 :         char *pszSQL = sqlite3_mprintf(
    6587             :             "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
    6588             :             "LIMIT 2",
    6589             :             pszIdentifier);
    6590           4 :         auto oResult = SQLQuery(hDB, pszSQL);
    6591           4 :         sqlite3_free(pszSQL);
    6592           8 :         if (oResult && oResult->RowCount() > 0 &&
    6593           9 :             oResult->GetValue(0, 0) != nullptr &&
    6594           1 :             !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
    6595             :         {
    6596           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6597             :                      "Identifier %s is already used by table %s", pszIdentifier,
    6598             :                      oResult->GetValue(0, 0));
    6599           1 :             return nullptr;
    6600             :         }
    6601             :     }
    6602             : 
    6603             :     /* Read GEOMETRY_NAME option */
    6604             :     const char *pszGeomColumnName =
    6605         733 :         CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
    6606         733 :     if (pszGeomColumnName == nullptr) /* deprecated name */
    6607         652 :         pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
    6608         733 :     if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
    6609             :     {
    6610         600 :         pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
    6611         600 :         if (pszGeomColumnName && pszGeomColumnName[0] == 0)
    6612         596 :             pszGeomColumnName = nullptr;
    6613             :     }
    6614         733 :     if (pszGeomColumnName == nullptr)
    6615         648 :         pszGeomColumnName = "geom";
    6616             :     const bool bGeomNullable =
    6617         733 :         CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
    6618             : 
    6619             :     /* Read FID option */
    6620         733 :     const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
    6621         733 :     if (pszFIDColumnName == nullptr)
    6622         698 :         pszFIDColumnName = "fid";
    6623             : 
    6624         733 :     if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
    6625             :     {
    6626         733 :         if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
    6627             :         {
    6628           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6629             :                      "The primary key (%s) name may not contain special "
    6630             :                      "characters or spaces",
    6631             :                      pszFIDColumnName);
    6632           0 :             return nullptr;
    6633             :         }
    6634             : 
    6635             :         /* Avoiding gpkg prefixes is not an official requirement, but seems wise
    6636             :          */
    6637         733 :         if (STARTS_WITH(osTableName.c_str(), "gpkg"))
    6638             :         {
    6639           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6640             :                      "The layer name may not begin with 'gpkg' as it is a "
    6641             :                      "reserved geopackage prefix");
    6642           0 :             return nullptr;
    6643             :         }
    6644             : 
    6645             :         /* Preemptively try and avoid sqlite3 syntax errors due to  */
    6646             :         /* illegal characters. */
    6647         733 :         if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
    6648             :             0)
    6649             :         {
    6650           0 :             CPLError(
    6651             :                 CE_Failure, CPLE_AppDefined,
    6652             :                 "The layer name may not contain special characters or spaces");
    6653           0 :             return nullptr;
    6654             :         }
    6655             :     }
    6656             : 
    6657             :     /* Check for any existing layers that already use this name */
    6658         934 :     for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
    6659             :          iLayer++)
    6660             :     {
    6661         202 :         if (EQUAL(osTableName.c_str(), m_apoLayers[iLayer]->GetName()))
    6662             :         {
    6663             :             const char *pszOverwrite =
    6664           2 :                 CSLFetchNameValue(papszOptions, "OVERWRITE");
    6665           2 :             if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
    6666             :             {
    6667           1 :                 DeleteLayer(iLayer);
    6668             :             }
    6669             :             else
    6670             :             {
    6671           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    6672             :                          "Layer %s already exists, CreateLayer failed.\n"
    6673             :                          "Use the layer creation option OVERWRITE=YES to "
    6674             :                          "replace it.",
    6675             :                          osTableName.c_str());
    6676           1 :                 return nullptr;
    6677             :             }
    6678             :         }
    6679             :     }
    6680             : 
    6681         732 :     if (m_apoLayers.size() == 1)
    6682             :     {
    6683             :         // Async RTree building doesn't play well with multiple layer:
    6684             :         // SQLite3 locks being hold for a long time, random failed commits,
    6685             :         // etc.
    6686          75 :         m_apoLayers[0]->FinishOrDisableThreadedRTree();
    6687             :     }
    6688             : 
    6689             :     /* Create a blank layer. */
    6690             :     auto poLayer =
    6691        1464 :         std::make_unique<OGRGeoPackageTableLayer>(this, osTableName.c_str());
    6692             : 
    6693         732 :     OGRSpatialReference *poSRS = nullptr;
    6694         732 :     if (poSpatialRef)
    6695             :     {
    6696         227 :         poSRS = poSpatialRef->Clone();
    6697         227 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6698             :     }
    6699        1465 :     poLayer->SetCreationParameters(
    6700             :         eGType,
    6701         733 :         bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
    6702             :         bGeomNullable, poSRS, CSLFetchNameValue(papszOptions, "SRID"),
    6703        1464 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
    6704             :                            : OGRGeomCoordinatePrecision(),
    6705         732 :         CPLTestBool(
    6706             :             CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
    6707         732 :         CPLTestBool(CSLFetchNameValueDef(
    6708             :             papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
    6709         733 :         bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
    6710             :         pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
    6711         732 :     if (poSRS)
    6712             :     {
    6713         227 :         poSRS->Release();
    6714             :     }
    6715             : 
    6716         732 :     poLayer->SetLaunder(bLaunder);
    6717             : 
    6718             :     /* Should we create a spatial index ? */
    6719         732 :     const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
    6720         732 :     int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
    6721         732 :     if (eGType != wkbNone && bCreateSpatialIndex)
    6722             :     {
    6723         658 :         poLayer->SetDeferredSpatialIndexCreation(true);
    6724             :     }
    6725             : 
    6726         732 :     poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
    6727         732 :     poLayer->SetTruncateFieldsFlag(
    6728         732 :         CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
    6729         732 :     if (eGType == wkbNone)
    6730             :     {
    6731          52 :         const char *pszASpatialVariant = CSLFetchNameValueDef(
    6732             :             papszOptions, "ASPATIAL_VARIANT",
    6733          52 :             m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
    6734             :                 ? "NOT_REGISTERED"
    6735             :                 : "GPKG_ATTRIBUTES");
    6736          52 :         GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
    6737          52 :         if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
    6738          40 :             eASpatialVariant = GPKG_ATTRIBUTES;
    6739          12 :         else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
    6740             :         {
    6741           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    6742             :                      "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
    6743           0 :             return nullptr;
    6744             :         }
    6745          12 :         else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
    6746          12 :             eASpatialVariant = NOT_REGISTERED;
    6747             :         else
    6748             :         {
    6749           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    6750             :                      "Unsupported value for ASPATIAL_VARIANT: %s",
    6751             :                      pszASpatialVariant);
    6752           0 :             return nullptr;
    6753             :         }
    6754          52 :         poLayer->SetASpatialVariant(eASpatialVariant);
    6755             :     }
    6756             : 
    6757             :     const char *pszDateTimePrecision =
    6758         732 :         CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
    6759         732 :     if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
    6760             :     {
    6761           2 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
    6762             :     }
    6763         730 :     else if (EQUAL(pszDateTimePrecision, "SECOND"))
    6764             :     {
    6765           1 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6766           0 :             CPLError(
    6767             :                 CE_Warning, CPLE_AppDefined,
    6768             :                 "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
    6769           1 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
    6770             :     }
    6771         729 :     else if (EQUAL(pszDateTimePrecision, "MINUTE"))
    6772             :     {
    6773           1 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6774           0 :             CPLError(
    6775             :                 CE_Warning, CPLE_AppDefined,
    6776             :                 "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
    6777           1 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
    6778             :     }
    6779         728 :     else if (EQUAL(pszDateTimePrecision, "AUTO"))
    6780             :     {
    6781         727 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6782          13 :             poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
    6783             :     }
    6784             :     else
    6785             :     {
    6786           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    6787             :                  "Unsupported value for DATETIME_PRECISION: %s",
    6788             :                  pszDateTimePrecision);
    6789           1 :         return nullptr;
    6790             :     }
    6791             : 
    6792             :     // If there was an ogr_empty_table table, we can remove it
    6793             :     // But do it at dataset closing, otherwise locking performance issues
    6794             :     // can arise (probably when transactions are used).
    6795         731 :     m_bRemoveOGREmptyTable = true;
    6796             : 
    6797         731 :     m_apoLayers.emplace_back(std::move(poLayer));
    6798         731 :     return m_apoLayers.back().get();
    6799             : }
    6800             : 
    6801             : /************************************************************************/
    6802             : /*                          FindLayerIndex()                            */
    6803             : /************************************************************************/
    6804             : 
    6805          27 : int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
    6806             : 
    6807             : {
    6808          42 :     for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
    6809             :          iLayer++)
    6810             :     {
    6811          28 :         if (EQUAL(pszLayerName, m_apoLayers[iLayer]->GetName()))
    6812          13 :             return iLayer;
    6813             :     }
    6814          14 :     return -1;
    6815             : }
    6816             : 
    6817             : /************************************************************************/
    6818             : /*                       DeleteLayerCommon()                            */
    6819             : /************************************************************************/
    6820             : 
    6821          39 : OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
    6822             : {
    6823             :     // Temporary remove foreign key checks
    6824             :     const GPKGTemporaryForeignKeyCheckDisabler
    6825          39 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    6826             : 
    6827          39 :     char *pszSQL = sqlite3_mprintf(
    6828             :         "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
    6829             :         pszLayerName);
    6830          39 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
    6831          39 :     sqlite3_free(pszSQL);
    6832             : 
    6833          39 :     if (eErr == OGRERR_NONE && HasExtensionsTable())
    6834             :     {
    6835          37 :         pszSQL = sqlite3_mprintf(
    6836             :             "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
    6837             :             pszLayerName);
    6838          37 :         eErr = SQLCommand(hDB, pszSQL);
    6839          37 :         sqlite3_free(pszSQL);
    6840             :     }
    6841             : 
    6842          39 :     if (eErr == OGRERR_NONE && HasMetadataTables())
    6843             :     {
    6844             :         // Delete from gpkg_metadata metadata records that are only referenced
    6845             :         // by the table we are about to drop
    6846          11 :         pszSQL = sqlite3_mprintf(
    6847             :             "DELETE FROM gpkg_metadata WHERE id IN ("
    6848             :             "SELECT DISTINCT md_file_id FROM "
    6849             :             "gpkg_metadata_reference WHERE "
    6850             :             "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
    6851             :             "AND id NOT IN ("
    6852             :             "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
    6853             :             "md_file_id IN (SELECT DISTINCT md_file_id FROM "
    6854             :             "gpkg_metadata_reference WHERE "
    6855             :             "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
    6856             :             "AND lower(table_name) <> lower('%q'))",
    6857             :             pszLayerName, pszLayerName, pszLayerName);
    6858          11 :         eErr = SQLCommand(hDB, pszSQL);
    6859          11 :         sqlite3_free(pszSQL);
    6860             : 
    6861          11 :         if (eErr == OGRERR_NONE)
    6862             :         {
    6863             :             pszSQL =
    6864          11 :                 sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
    6865             :                                 "lower(table_name) = lower('%q')",
    6866             :                                 pszLayerName);
    6867          11 :             eErr = SQLCommand(hDB, pszSQL);
    6868          11 :             sqlite3_free(pszSQL);
    6869             :         }
    6870             :     }
    6871             : 
    6872          39 :     if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
    6873             :     {
    6874             :         // Remove reference to potential corresponding mapping table in
    6875             :         // gpkg_extensions
    6876           4 :         pszSQL = sqlite3_mprintf(
    6877             :             "DELETE FROM gpkg_extensions WHERE "
    6878             :             "extension_name IN ('related_tables', "
    6879             :             "'gpkg_related_tables') AND lower(table_name) = "
    6880             :             "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
    6881             :             "lower(base_table_name) = lower('%q') OR "
    6882             :             "lower(related_table_name) = lower('%q') OR "
    6883             :             "lower(mapping_table_name) = lower('%q'))",
    6884             :             pszLayerName, pszLayerName, pszLayerName);
    6885           4 :         eErr = SQLCommand(hDB, pszSQL);
    6886           4 :         sqlite3_free(pszSQL);
    6887             : 
    6888           4 :         if (eErr == OGRERR_NONE)
    6889             :         {
    6890             :             // Remove reference to potential corresponding mapping table in
    6891             :             // gpkgext_relations
    6892             :             pszSQL =
    6893           4 :                 sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
    6894             :                                 "lower(base_table_name) = lower('%q') OR "
    6895             :                                 "lower(related_table_name) = lower('%q') OR "
    6896             :                                 "lower(mapping_table_name) = lower('%q')",
    6897             :                                 pszLayerName, pszLayerName, pszLayerName);
    6898           4 :             eErr = SQLCommand(hDB, pszSQL);
    6899           4 :             sqlite3_free(pszSQL);
    6900             :         }
    6901             : 
    6902           4 :         if (eErr == OGRERR_NONE && HasExtensionsTable())
    6903             :         {
    6904             :             // If there is no longer any mapping table, then completely
    6905             :             // remove any reference to the extension in gpkg_extensions
    6906             :             // as mandated per the related table specification.
    6907             :             OGRErr err;
    6908           4 :             if (SQLGetInteger(hDB,
    6909             :                               "SELECT COUNT(*) FROM gpkg_extensions WHERE "
    6910             :                               "extension_name IN ('related_tables', "
    6911             :                               "'gpkg_related_tables') AND "
    6912             :                               "lower(table_name) != 'gpkgext_relations'",
    6913           4 :                               &err) == 0)
    6914             :             {
    6915           2 :                 eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
    6916             :                                        "extension_name IN ('related_tables', "
    6917             :                                        "'gpkg_related_tables')");
    6918             :             }
    6919             : 
    6920           4 :             ClearCachedRelationships();
    6921             :         }
    6922             :     }
    6923             : 
    6924          39 :     if (eErr == OGRERR_NONE)
    6925             :     {
    6926          39 :         pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
    6927          39 :         eErr = SQLCommand(hDB, pszSQL);
    6928          39 :         sqlite3_free(pszSQL);
    6929             :     }
    6930             : 
    6931             :     // Check foreign key integrity
    6932          39 :     if (eErr == OGRERR_NONE)
    6933             :     {
    6934          39 :         eErr = PragmaCheck("foreign_key_check", "", 0);
    6935             :     }
    6936             : 
    6937          78 :     return eErr;
    6938             : }
    6939             : 
    6940             : /************************************************************************/
    6941             : /*                            DeleteLayer()                             */
    6942             : /************************************************************************/
    6943             : 
    6944          36 : OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
    6945             : {
    6946          71 :     if (!GetUpdate() || iLayer < 0 ||
    6947          35 :         iLayer >= static_cast<int>(m_apoLayers.size()))
    6948           2 :         return OGRERR_FAILURE;
    6949             : 
    6950          34 :     m_apoLayers[iLayer]->ResetReading();
    6951          34 :     m_apoLayers[iLayer]->SyncToDisk();
    6952             : 
    6953          68 :     CPLString osLayerName = m_apoLayers[iLayer]->GetName();
    6954             : 
    6955          34 :     CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
    6956             : 
    6957             :     // Temporary remove foreign key checks
    6958             :     const GPKGTemporaryForeignKeyCheckDisabler
    6959          34 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    6960             : 
    6961          34 :     OGRErr eErr = SoftStartTransaction();
    6962             : 
    6963          34 :     if (eErr == OGRERR_NONE)
    6964             :     {
    6965          34 :         if (m_apoLayers[iLayer]->HasSpatialIndex())
    6966          31 :             m_apoLayers[iLayer]->DropSpatialIndex();
    6967             : 
    6968             :         char *pszSQL =
    6969          34 :             sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
    6970             :                             "lower(table_name) = lower('%q')",
    6971             :                             osLayerName.c_str());
    6972          34 :         eErr = SQLCommand(hDB, pszSQL);
    6973          34 :         sqlite3_free(pszSQL);
    6974             :     }
    6975             : 
    6976          34 :     if (eErr == OGRERR_NONE && HasDataColumnsTable())
    6977             :     {
    6978           1 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
    6979             :                                        "lower(table_name) = lower('%q')",
    6980             :                                        osLayerName.c_str());
    6981           1 :         eErr = SQLCommand(hDB, pszSQL);
    6982           1 :         sqlite3_free(pszSQL);
    6983             :     }
    6984             : 
    6985             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    6986          34 :     if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
    6987             :     {
    6988          34 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
    6989             :                                        "lower(table_name) = lower('%q')",
    6990             :                                        osLayerName.c_str());
    6991          34 :         eErr = SQLCommand(hDB, pszSQL);
    6992          34 :         sqlite3_free(pszSQL);
    6993             :     }
    6994             : #endif
    6995             : 
    6996          34 :     if (eErr == OGRERR_NONE)
    6997             :     {
    6998          34 :         eErr = DeleteLayerCommon(osLayerName.c_str());
    6999             :     }
    7000             : 
    7001          34 :     if (eErr == OGRERR_NONE)
    7002             :     {
    7003          34 :         eErr = SoftCommitTransaction();
    7004          34 :         if (eErr == OGRERR_NONE)
    7005             :         {
    7006             :             /* Delete the layer object */
    7007          34 :             m_apoLayers.erase(m_apoLayers.begin() + iLayer);
    7008             :         }
    7009             :     }
    7010             :     else
    7011             :     {
    7012           0 :         SoftRollbackTransaction();
    7013             :     }
    7014             : 
    7015          34 :     return eErr;
    7016             : }
    7017             : 
    7018             : /************************************************************************/
    7019             : /*                       DeleteRasterLayer()                            */
    7020             : /************************************************************************/
    7021             : 
    7022           2 : OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
    7023             : {
    7024             :     // Temporary remove foreign key checks
    7025             :     const GPKGTemporaryForeignKeyCheckDisabler
    7026           2 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7027             : 
    7028           2 :     OGRErr eErr = SoftStartTransaction();
    7029             : 
    7030           2 :     if (eErr == OGRERR_NONE)
    7031             :     {
    7032           2 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix WHERE "
    7033             :                                        "lower(table_name) = lower('%q')",
    7034             :                                        pszLayerName);
    7035           2 :         eErr = SQLCommand(hDB, pszSQL);
    7036           2 :         sqlite3_free(pszSQL);
    7037             :     }
    7038             : 
    7039           2 :     if (eErr == OGRERR_NONE)
    7040             :     {
    7041           2 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
    7042             :                                        "lower(table_name) = lower('%q')",
    7043             :                                        pszLayerName);
    7044           2 :         eErr = SQLCommand(hDB, pszSQL);
    7045           2 :         sqlite3_free(pszSQL);
    7046             :     }
    7047             : 
    7048           2 :     if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
    7049             :     {
    7050             :         char *pszSQL =
    7051           1 :             sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
    7052             :                             "WHERE lower(tile_matrix_set_name) = lower('%q')",
    7053             :                             pszLayerName);
    7054           1 :         eErr = SQLCommand(hDB, pszSQL);
    7055           1 :         sqlite3_free(pszSQL);
    7056             : 
    7057           1 :         if (eErr == OGRERR_NONE)
    7058             :         {
    7059             :             pszSQL =
    7060           1 :                 sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
    7061             :                                 "WHERE lower(tpudt_name) = lower('%q')",
    7062             :                                 pszLayerName);
    7063           1 :             eErr = SQLCommand(hDB, pszSQL);
    7064           1 :             sqlite3_free(pszSQL);
    7065             :         }
    7066             :     }
    7067             : 
    7068           2 :     if (eErr == OGRERR_NONE)
    7069             :     {
    7070           2 :         eErr = DeleteLayerCommon(pszLayerName);
    7071             :     }
    7072             : 
    7073           2 :     if (eErr == OGRERR_NONE)
    7074             :     {
    7075           2 :         eErr = SoftCommitTransaction();
    7076             :     }
    7077             :     else
    7078             :     {
    7079           0 :         SoftRollbackTransaction();
    7080             :     }
    7081             : 
    7082           4 :     return eErr;
    7083             : }
    7084             : 
    7085             : /************************************************************************/
    7086             : /*                    DeleteVectorOrRasterLayer()                       */
    7087             : /************************************************************************/
    7088             : 
    7089          13 : bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
    7090             : {
    7091             : 
    7092          13 :     int idx = FindLayerIndex(pszLayerName);
    7093          13 :     if (idx >= 0)
    7094             :     {
    7095           5 :         DeleteLayer(idx);
    7096           5 :         return true;
    7097             :     }
    7098             : 
    7099             :     char *pszSQL =
    7100           8 :         sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
    7101             :                         "lower(table_name) = lower('%q') "
    7102             :                         "AND data_type IN ('tiles', '2d-gridded-coverage')",
    7103             :                         pszLayerName);
    7104           8 :     bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
    7105           8 :     sqlite3_free(pszSQL);
    7106           8 :     if (bIsRasterTable)
    7107             :     {
    7108           2 :         DeleteRasterLayer(pszLayerName);
    7109           2 :         return true;
    7110             :     }
    7111           6 :     return false;
    7112             : }
    7113             : 
    7114           7 : bool GDALGeoPackageDataset::RenameVectorOrRasterLayer(
    7115             :     const char *pszLayerName, const char *pszNewLayerName)
    7116             : {
    7117           7 :     int idx = FindLayerIndex(pszLayerName);
    7118           7 :     if (idx >= 0)
    7119             :     {
    7120           4 :         m_apoLayers[idx]->Rename(pszNewLayerName);
    7121           4 :         return true;
    7122             :     }
    7123             : 
    7124             :     char *pszSQL =
    7125           3 :         sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
    7126             :                         "lower(table_name) = lower('%q') "
    7127             :                         "AND data_type IN ('tiles', '2d-gridded-coverage')",
    7128             :                         pszLayerName);
    7129           3 :     const bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
    7130           3 :     sqlite3_free(pszSQL);
    7131             : 
    7132           3 :     if (bIsRasterTable)
    7133             :     {
    7134           2 :         return RenameRasterLayer(pszLayerName, pszNewLayerName);
    7135             :     }
    7136             : 
    7137           1 :     return false;
    7138             : }
    7139             : 
    7140           2 : bool GDALGeoPackageDataset::RenameRasterLayer(const char *pszLayerName,
    7141             :                                               const char *pszNewLayerName)
    7142             : {
    7143           4 :     std::string osSQL;
    7144             : 
    7145           2 :     char *pszSQL = sqlite3_mprintf(
    7146             :         "SELECT 1 FROM sqlite_master WHERE lower(name) = lower('%q') "
    7147             :         "AND type IN ('table', 'view')",
    7148             :         pszNewLayerName);
    7149           2 :     const bool bAlreadyExists = SQLGetInteger(GetDB(), pszSQL, nullptr) == 1;
    7150           2 :     sqlite3_free(pszSQL);
    7151           2 :     if (bAlreadyExists)
    7152             :     {
    7153           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists",
    7154             :                  pszNewLayerName);
    7155           0 :         return false;
    7156             :     }
    7157             : 
    7158             :     // Temporary remove foreign key checks
    7159             :     const GPKGTemporaryForeignKeyCheckDisabler
    7160           4 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7161             : 
    7162           2 :     if (SoftStartTransaction() != OGRERR_NONE)
    7163             :     {
    7164           0 :         return false;
    7165             :     }
    7166             : 
    7167           2 :     pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET table_name = '%q' WHERE "
    7168             :                              "lower(table_name) = lower('%q');",
    7169             :                              pszNewLayerName, pszLayerName);
    7170           2 :     osSQL = pszSQL;
    7171           2 :     sqlite3_free(pszSQL);
    7172             : 
    7173           2 :     pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' WHERE "
    7174             :                              "lower(identifier) = lower('%q');",
    7175             :                              pszNewLayerName, pszLayerName);
    7176           2 :     osSQL += pszSQL;
    7177           2 :     sqlite3_free(pszSQL);
    7178             : 
    7179             :     pszSQL =
    7180           2 :         sqlite3_mprintf("UPDATE gpkg_tile_matrix SET table_name = '%q' WHERE "
    7181             :                         "lower(table_name) = lower('%q');",
    7182             :                         pszNewLayerName, pszLayerName);
    7183           2 :     osSQL += pszSQL;
    7184           2 :     sqlite3_free(pszSQL);
    7185             : 
    7186           2 :     pszSQL = sqlite3_mprintf(
    7187             :         "UPDATE gpkg_tile_matrix_set SET table_name = '%q' WHERE "
    7188             :         "lower(table_name) = lower('%q');",
    7189             :         pszNewLayerName, pszLayerName);
    7190           2 :     osSQL += pszSQL;
    7191           2 :     sqlite3_free(pszSQL);
    7192             : 
    7193           2 :     if (HasGriddedCoverageAncillaryTable())
    7194             :     {
    7195           1 :         pszSQL = sqlite3_mprintf("UPDATE gpkg_2d_gridded_coverage_ancillary "
    7196             :                                  "SET tile_matrix_set_name = '%q' WHERE "
    7197             :                                  "lower(tile_matrix_set_name) = lower('%q');",
    7198             :                                  pszNewLayerName, pszLayerName);
    7199           1 :         osSQL += pszSQL;
    7200           1 :         sqlite3_free(pszSQL);
    7201             : 
    7202           1 :         pszSQL = sqlite3_mprintf(
    7203             :             "UPDATE gpkg_2d_gridded_tile_ancillary SET tpudt_name = '%q' WHERE "
    7204             :             "lower(tpudt_name) = lower('%q');",
    7205             :             pszNewLayerName, pszLayerName);
    7206           1 :         osSQL += pszSQL;
    7207           1 :         sqlite3_free(pszSQL);
    7208             :     }
    7209             : 
    7210           2 :     if (HasExtensionsTable())
    7211             :     {
    7212           2 :         pszSQL = sqlite3_mprintf(
    7213             :             "UPDATE gpkg_extensions SET table_name = '%q' WHERE "
    7214             :             "lower(table_name) = lower('%q');",
    7215             :             pszNewLayerName, pszLayerName);
    7216           2 :         osSQL += pszSQL;
    7217           2 :         sqlite3_free(pszSQL);
    7218             :     }
    7219             : 
    7220           2 :     if (HasMetadataTables())
    7221             :     {
    7222           1 :         pszSQL = sqlite3_mprintf(
    7223             :             "UPDATE gpkg_metadata_reference SET table_name = '%q' WHERE "
    7224             :             "lower(table_name) = lower('%q');",
    7225             :             pszNewLayerName, pszLayerName);
    7226           1 :         osSQL += pszSQL;
    7227           1 :         sqlite3_free(pszSQL);
    7228             :     }
    7229             : 
    7230           2 :     if (HasDataColumnsTable())
    7231             :     {
    7232           0 :         pszSQL = sqlite3_mprintf(
    7233             :             "UPDATE gpkg_data_columns SET table_name = '%q' WHERE "
    7234             :             "lower(table_name) = lower('%q');",
    7235             :             pszNewLayerName, pszLayerName);
    7236           0 :         osSQL += pszSQL;
    7237           0 :         sqlite3_free(pszSQL);
    7238             :     }
    7239             : 
    7240           2 :     if (HasQGISLayerStyles())
    7241             :     {
    7242             :         // Update QGIS styles
    7243             :         pszSQL =
    7244           0 :             sqlite3_mprintf("UPDATE layer_styles SET f_table_name = '%q' WHERE "
    7245             :                             "lower(f_table_name) = lower('%q');",
    7246             :                             pszNewLayerName, pszLayerName);
    7247           0 :         osSQL += pszSQL;
    7248           0 :         sqlite3_free(pszSQL);
    7249             :     }
    7250             : 
    7251             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7252           2 :     if (m_bHasGPKGOGRContents)
    7253             :     {
    7254           2 :         pszSQL = sqlite3_mprintf(
    7255             :             "UPDATE gpkg_ogr_contents SET table_name = '%q' WHERE "
    7256             :             "lower(table_name) = lower('%q');",
    7257             :             pszNewLayerName, pszLayerName);
    7258           2 :         osSQL += pszSQL;
    7259           2 :         sqlite3_free(pszSQL);
    7260             :     }
    7261             : #endif
    7262             : 
    7263           2 :     if (HasGpkgextRelationsTable())
    7264             :     {
    7265           0 :         pszSQL = sqlite3_mprintf(
    7266             :             "UPDATE gpkgext_relations SET base_table_name = '%q' WHERE "
    7267             :             "lower(base_table_name) = lower('%q');",
    7268             :             pszNewLayerName, pszLayerName);
    7269           0 :         osSQL += pszSQL;
    7270           0 :         sqlite3_free(pszSQL);
    7271             : 
    7272           0 :         pszSQL = sqlite3_mprintf(
    7273             :             "UPDATE gpkgext_relations SET related_table_name = '%q' WHERE "
    7274             :             "lower(related_table_name) = lower('%q');",
    7275             :             pszNewLayerName, pszLayerName);
    7276           0 :         osSQL += pszSQL;
    7277           0 :         sqlite3_free(pszSQL);
    7278             : 
    7279           0 :         pszSQL = sqlite3_mprintf(
    7280             :             "UPDATE gpkgext_relations SET mapping_table_name = '%q' WHERE "
    7281             :             "lower(mapping_table_name) = lower('%q');",
    7282             :             pszNewLayerName, pszLayerName);
    7283           0 :         osSQL += pszSQL;
    7284           0 :         sqlite3_free(pszSQL);
    7285             :     }
    7286             : 
    7287             :     // Drop all triggers for the layer
    7288           2 :     pszSQL = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type = "
    7289             :                              "'trigger' AND tbl_name = '%q'",
    7290             :                              pszLayerName);
    7291           2 :     auto oTriggerResult = SQLQuery(GetDB(), pszSQL);
    7292           2 :     sqlite3_free(pszSQL);
    7293           2 :     if (oTriggerResult)
    7294             :     {
    7295          14 :         for (int i = 0; i < oTriggerResult->RowCount(); i++)
    7296             :         {
    7297          12 :             const char *pszTriggerName = oTriggerResult->GetValue(0, i);
    7298          12 :             pszSQL = sqlite3_mprintf("DROP TRIGGER IF EXISTS \"%w\";",
    7299             :                                      pszTriggerName);
    7300          12 :             osSQL += pszSQL;
    7301          12 :             sqlite3_free(pszSQL);
    7302             :         }
    7303             :     }
    7304             : 
    7305           2 :     pszSQL = sqlite3_mprintf("ALTER TABLE \"%w\" RENAME TO \"%w\";",
    7306             :                              pszLayerName, pszNewLayerName);
    7307           2 :     osSQL += pszSQL;
    7308           2 :     sqlite3_free(pszSQL);
    7309             : 
    7310             :     // Recreate all zoom/tile triggers
    7311           2 :     if (oTriggerResult)
    7312             :     {
    7313           2 :         osSQL += CreateRasterTriggersSQL(pszNewLayerName);
    7314             :     }
    7315             : 
    7316           2 :     OGRErr eErr = SQLCommand(GetDB(), osSQL.c_str());
    7317             : 
    7318             :     // Check foreign key integrity
    7319           2 :     if (eErr == OGRERR_NONE)
    7320             :     {
    7321           2 :         eErr = PragmaCheck("foreign_key_check", "", 0);
    7322             :     }
    7323             : 
    7324           2 :     if (eErr == OGRERR_NONE)
    7325             :     {
    7326           2 :         eErr = SoftCommitTransaction();
    7327             :     }
    7328             :     else
    7329             :     {
    7330           0 :         SoftRollbackTransaction();
    7331             :     }
    7332             : 
    7333           2 :     return eErr == OGRERR_NONE;
    7334             : }
    7335             : 
    7336             : /************************************************************************/
    7337             : /*                       TestCapability()                               */
    7338             : /************************************************************************/
    7339             : 
    7340         433 : int GDALGeoPackageDataset::TestCapability(const char *pszCap)
    7341             : {
    7342         433 :     if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
    7343         272 :         EQUAL(pszCap, "RenameLayer"))
    7344             :     {
    7345         161 :         return GetUpdate();
    7346             :     }
    7347         272 :     else if (EQUAL(pszCap, ODsCCurveGeometries))
    7348          12 :         return TRUE;
    7349         260 :     else if (EQUAL(pszCap, ODsCMeasuredGeometries))
    7350           8 :         return TRUE;
    7351         252 :     else if (EQUAL(pszCap, ODsCZGeometries))
    7352           8 :         return TRUE;
    7353         244 :     else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
    7354         244 :              EQUAL(pszCap, GDsCAddRelationship) ||
    7355         244 :              EQUAL(pszCap, GDsCDeleteRelationship) ||
    7356         244 :              EQUAL(pszCap, GDsCUpdateRelationship) ||
    7357         244 :              EQUAL(pszCap, ODsCAddFieldDomain))
    7358           1 :         return GetUpdate();
    7359             : 
    7360         243 :     return OGRSQLiteBaseDataSource::TestCapability(pszCap);
    7361             : }
    7362             : 
    7363             : /************************************************************************/
    7364             : /*                       ResetReadingAllLayers()                        */
    7365             : /************************************************************************/
    7366             : 
    7367         204 : void GDALGeoPackageDataset::ResetReadingAllLayers()
    7368             : {
    7369         413 :     for (auto &poLayer : m_apoLayers)
    7370             :     {
    7371         209 :         poLayer->ResetReading();
    7372             :     }
    7373         204 : }
    7374             : 
    7375             : /************************************************************************/
    7376             : /*                             ExecuteSQL()                             */
    7377             : /************************************************************************/
    7378             : 
    7379             : static const char *const apszFuncsWithSideEffects[] = {
    7380             :     "CreateSpatialIndex",
    7381             :     "DisableSpatialIndex",
    7382             :     "HasSpatialIndex",
    7383             :     "RegisterGeometryExtension",
    7384             : };
    7385             : 
    7386        5640 : OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
    7387             :                                             OGRGeometry *poSpatialFilter,
    7388             :                                             const char *pszDialect)
    7389             : 
    7390             : {
    7391        5640 :     m_bHasReadMetadataFromStorage = false;
    7392             : 
    7393        5640 :     FlushMetadata();
    7394             : 
    7395        5658 :     while (*pszSQLCommand != '\0' &&
    7396        5658 :            isspace(static_cast<unsigned char>(*pszSQLCommand)))
    7397          18 :         pszSQLCommand++;
    7398             : 
    7399       11280 :     CPLString osSQLCommand(pszSQLCommand);
    7400        5640 :     if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
    7401          48 :         osSQLCommand.pop_back();
    7402             : 
    7403        5640 :     if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
    7404             :     {
    7405             :         // Some SQL commands will influence the feature count behind our
    7406             :         // back, so disable it in that case.
    7407             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7408             :         const bool bInsertOrDelete =
    7409        5571 :             osSQLCommand.ifind("insert into ") != std::string::npos ||
    7410        2450 :             osSQLCommand.ifind("insert or replace into ") !=
    7411        8021 :                 std::string::npos ||
    7412        2413 :             osSQLCommand.ifind("delete from ") != std::string::npos;
    7413             :         const bool bRollback =
    7414        5571 :             osSQLCommand.ifind("rollback ") != std::string::npos;
    7415             : #endif
    7416             : 
    7417        7389 :         for (auto &poLayer : m_apoLayers)
    7418             :         {
    7419        1818 :             if (poLayer->SyncToDisk() != OGRERR_NONE)
    7420           0 :                 return nullptr;
    7421             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7422        2020 :             if (bRollback ||
    7423         202 :                 (bInsertOrDelete &&
    7424         202 :                  osSQLCommand.ifind(poLayer->GetName()) != std::string::npos))
    7425             :             {
    7426         200 :                 poLayer->DisableFeatureCount();
    7427             :             }
    7428             : #endif
    7429             :         }
    7430             :     }
    7431             : 
    7432        5640 :     if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
    7433        5639 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
    7434        5639 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
    7435        5639 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
    7436             :     {
    7437           1 :         OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
    7438             :     }
    7439        5639 :     else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
    7440        5638 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
    7441        5638 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
    7442        5638 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
    7443             :     {
    7444           1 :         OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
    7445             :     }
    7446             : 
    7447             :     /* -------------------------------------------------------------------- */
    7448             :     /*      DEBUG "SELECT nolock" command.                                  */
    7449             :     /* -------------------------------------------------------------------- */
    7450        5709 :     if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
    7451          69 :         EQUAL(osSQLCommand, "SELECT nolock"))
    7452             :     {
    7453           3 :         return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
    7454             :     }
    7455             : 
    7456             :     /* -------------------------------------------------------------------- */
    7457             :     /*      Special case DELLAYER: command.                                 */
    7458             :     /* -------------------------------------------------------------------- */
    7459        5637 :     if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
    7460             :     {
    7461           4 :         const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
    7462             : 
    7463           4 :         while (*pszLayerName == ' ')
    7464           0 :             pszLayerName++;
    7465             : 
    7466           4 :         if (!DeleteVectorOrRasterLayer(pszLayerName))
    7467             :         {
    7468           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
    7469             :                      pszLayerName);
    7470             :         }
    7471           4 :         return nullptr;
    7472             :     }
    7473             : 
    7474             :     /* -------------------------------------------------------------------- */
    7475             :     /*      Special case RECOMPUTE EXTENT ON command.                       */
    7476             :     /* -------------------------------------------------------------------- */
    7477        5633 :     if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
    7478             :     {
    7479             :         const char *pszLayerName =
    7480           4 :             osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
    7481             : 
    7482           4 :         while (*pszLayerName == ' ')
    7483           0 :             pszLayerName++;
    7484             : 
    7485           4 :         int idx = FindLayerIndex(pszLayerName);
    7486           4 :         if (idx >= 0)
    7487             :         {
    7488           4 :             m_apoLayers[idx]->RecomputeExtent();
    7489             :         }
    7490             :         else
    7491           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
    7492             :                      pszLayerName);
    7493           4 :         return nullptr;
    7494             :     }
    7495             : 
    7496             :     /* -------------------------------------------------------------------- */
    7497             :     /*      Intercept DROP TABLE                                            */
    7498             :     /* -------------------------------------------------------------------- */
    7499        5629 :     if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
    7500             :     {
    7501           9 :         const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
    7502             : 
    7503           9 :         while (*pszLayerName == ' ')
    7504           0 :             pszLayerName++;
    7505             : 
    7506           9 :         if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
    7507           4 :             return nullptr;
    7508             :     }
    7509             : 
    7510             :     /* -------------------------------------------------------------------- */
    7511             :     /*      Intercept ALTER TABLE src_table RENAME TO dst_table             */
    7512             :     /*      and       ALTER TABLE table RENAME COLUMN src_name TO dst_name  */
    7513             :     /*      and       ALTER TABLE table DROP COLUMN col_name                */
    7514             :     /*                                                                      */
    7515             :     /*      We do this because SQLite mechanisms can't deal with updating   */
    7516             :     /*      literal values in gpkg_ tables that refer to table and column   */
    7517             :     /*      names.                                                          */
    7518             :     /* -------------------------------------------------------------------- */
    7519        5625 :     if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
    7520             :     {
    7521           9 :         char **papszTokens = SQLTokenize(osSQLCommand);
    7522             :         /* ALTER TABLE src_table RENAME TO dst_table */
    7523          16 :         if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
    7524           7 :             EQUAL(papszTokens[4], "TO"))
    7525             :         {
    7526           7 :             const char *pszSrcTableName = papszTokens[2];
    7527           7 :             const char *pszDstTableName = papszTokens[5];
    7528           7 :             if (RenameVectorOrRasterLayer(SQLUnescape(pszSrcTableName),
    7529          14 :                                           SQLUnescape(pszDstTableName)))
    7530             :             {
    7531           6 :                 CSLDestroy(papszTokens);
    7532           6 :                 return nullptr;
    7533             :             }
    7534             :         }
    7535             :         /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
    7536           2 :         else if (CSLCount(papszTokens) == 8 &&
    7537           1 :                  EQUAL(papszTokens[3], "RENAME") &&
    7538           3 :                  EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
    7539             :         {
    7540           1 :             const char *pszTableName = papszTokens[2];
    7541           1 :             const char *pszSrcColumn = papszTokens[5];
    7542           1 :             const char *pszDstColumn = papszTokens[7];
    7543             :             OGRGeoPackageTableLayer *poLayer =
    7544           0 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7545           1 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7546           1 :             if (poLayer)
    7547             :             {
    7548           2 :                 int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
    7549           2 :                     SQLUnescape(pszSrcColumn));
    7550           1 :                 if (nSrcFieldIdx >= 0)
    7551             :                 {
    7552             :                     // OFTString or any type will do as we just alter the name
    7553             :                     // so it will be ignored.
    7554           1 :                     OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
    7555           1 :                                             OFTString);
    7556           1 :                     poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
    7557             :                                             ALTER_NAME_FLAG);
    7558           1 :                     CSLDestroy(papszTokens);
    7559           1 :                     return nullptr;
    7560             :                 }
    7561             :             }
    7562             :         }
    7563             :         /* ALTER TABLE table DROP COLUMN col_name */
    7564           2 :         else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
    7565           1 :                  EQUAL(papszTokens[4], "COLUMN"))
    7566             :         {
    7567           1 :             const char *pszTableName = papszTokens[2];
    7568           1 :             const char *pszColumnName = papszTokens[5];
    7569             :             OGRGeoPackageTableLayer *poLayer =
    7570           0 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7571           1 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7572           1 :             if (poLayer)
    7573             :             {
    7574           2 :                 int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
    7575           2 :                     SQLUnescape(pszColumnName));
    7576           1 :                 if (nFieldIdx >= 0)
    7577             :                 {
    7578           1 :                     poLayer->DeleteField(nFieldIdx);
    7579           1 :                     CSLDestroy(papszTokens);
    7580           1 :                     return nullptr;
    7581             :                 }
    7582             :             }
    7583             :         }
    7584           1 :         CSLDestroy(papszTokens);
    7585             :     }
    7586             : 
    7587        5617 :     if (ProcessTransactionSQL(osSQLCommand))
    7588             :     {
    7589         253 :         return nullptr;
    7590             :     }
    7591             : 
    7592        5364 :     if (EQUAL(osSQLCommand, "VACUUM"))
    7593             :     {
    7594          13 :         ResetReadingAllLayers();
    7595             :     }
    7596        5351 :     else if (STARTS_WITH_CI(osSQLCommand, "DELETE FROM "))
    7597             :     {
    7598             :         // Optimize truncation of a table, especially if it has a spatial
    7599             :         // index.
    7600          21 :         const CPLStringList aosTokens(SQLTokenize(osSQLCommand));
    7601          21 :         if (aosTokens.size() == 3)
    7602             :         {
    7603          15 :             const char *pszTableName = aosTokens[2];
    7604             :             OGRGeoPackageTableLayer *poLayer =
    7605           8 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7606          23 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7607          15 :             if (poLayer)
    7608             :             {
    7609           7 :                 poLayer->Truncate();
    7610           7 :                 return nullptr;
    7611             :             }
    7612             :         }
    7613             :     }
    7614        5330 :     else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
    7615           1 :         return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
    7616        5329 :     else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
    7617          66 :              !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
    7618          66 :              !EQUAL(pszDialect, "DEBUG"))
    7619           0 :         return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
    7620           0 :                                        pszDialect);
    7621             : 
    7622             :     /* -------------------------------------------------------------------- */
    7623             :     /*      Prepare statement.                                              */
    7624             :     /* -------------------------------------------------------------------- */
    7625        5356 :     sqlite3_stmt *hSQLStmt = nullptr;
    7626             : 
    7627             :     /* This will speed-up layer creation */
    7628             :     /* ORDER BY are costly to evaluate and are not necessary to establish */
    7629             :     /* the layer definition. */
    7630        5356 :     bool bUseStatementForGetNextFeature = true;
    7631        5356 :     bool bEmptyLayer = false;
    7632       10712 :     CPLString osSQLCommandTruncated(osSQLCommand);
    7633             : 
    7634       17670 :     if (osSQLCommand.ifind("SELECT ") == 0 &&
    7635        6157 :         CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
    7636         767 :             std::string::npos &&
    7637         767 :         osSQLCommand.ifind(" UNION ") == std::string::npos &&
    7638        6924 :         osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
    7639         767 :         osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
    7640             :     {
    7641         767 :         size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
    7642         767 :         if (nOrderByPos != std::string::npos)
    7643             :         {
    7644           9 :             osSQLCommandTruncated.resize(nOrderByPos);
    7645           9 :             bUseStatementForGetNextFeature = false;
    7646             :         }
    7647             :     }
    7648             : 
    7649        5356 :     int rc = prepareSql(hDB, osSQLCommandTruncated.c_str(),
    7650        5356 :                         static_cast<int>(osSQLCommandTruncated.size()),
    7651             :                         &hSQLStmt, nullptr);
    7652             : 
    7653        5356 :     if (rc != SQLITE_OK)
    7654             :     {
    7655           9 :         CPLError(CE_Failure, CPLE_AppDefined,
    7656             :                  "In ExecuteSQL(): sqlite3_prepare_v2(%s): %s",
    7657             :                  osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
    7658             : 
    7659           9 :         if (hSQLStmt != nullptr)
    7660             :         {
    7661           0 :             sqlite3_finalize(hSQLStmt);
    7662             :         }
    7663             : 
    7664           9 :         return nullptr;
    7665             :     }
    7666             : 
    7667             :     /* -------------------------------------------------------------------- */
    7668             :     /*      Do we get a resultset?                                          */
    7669             :     /* -------------------------------------------------------------------- */
    7670        5347 :     rc = sqlite3_step(hSQLStmt);
    7671             : 
    7672        6933 :     for (auto &poLayer : m_apoLayers)
    7673             :     {
    7674        1586 :         poLayer->RunDeferredDropRTreeTableIfNecessary();
    7675             :     }
    7676             : 
    7677        5347 :     if (rc != SQLITE_ROW)
    7678             :     {
    7679        4627 :         if (rc != SQLITE_DONE)
    7680             :         {
    7681           7 :             CPLError(CE_Failure, CPLE_AppDefined,
    7682             :                      "In ExecuteSQL(): sqlite3_step(%s):\n  %s",
    7683             :                      osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
    7684             : 
    7685           7 :             sqlite3_finalize(hSQLStmt);
    7686           7 :             return nullptr;
    7687             :         }
    7688             : 
    7689        4620 :         if (EQUAL(osSQLCommand, "VACUUM"))
    7690             :         {
    7691          13 :             sqlite3_finalize(hSQLStmt);
    7692             :             /* VACUUM rewrites the DB, so we need to reset the application id */
    7693          13 :             SetApplicationAndUserVersionId();
    7694          13 :             return nullptr;
    7695             :         }
    7696             : 
    7697        4607 :         if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
    7698             :         {
    7699        4482 :             sqlite3_finalize(hSQLStmt);
    7700        4482 :             return nullptr;
    7701             :         }
    7702             : 
    7703         125 :         bUseStatementForGetNextFeature = false;
    7704         125 :         bEmptyLayer = true;
    7705             :     }
    7706             : 
    7707             :     /* -------------------------------------------------------------------- */
    7708             :     /*      Special case for some functions which must be run               */
    7709             :     /*      only once                                                       */
    7710             :     /* -------------------------------------------------------------------- */
    7711         845 :     if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
    7712             :     {
    7713        3849 :         for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
    7714             :                                          sizeof(apszFuncsWithSideEffects[0]);
    7715             :              i++)
    7716             :         {
    7717        3105 :             if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
    7718             :                        strlen(apszFuncsWithSideEffects[i])))
    7719             :             {
    7720         112 :                 if (sqlite3_column_count(hSQLStmt) == 1 &&
    7721          56 :                     sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
    7722             :                 {
    7723          56 :                     int ret = sqlite3_column_int(hSQLStmt, 0);
    7724             : 
    7725          56 :                     sqlite3_finalize(hSQLStmt);
    7726             : 
    7727             :                     return new OGRSQLiteSingleFeatureLayer(
    7728          56 :                         apszFuncsWithSideEffects[i], ret);
    7729             :                 }
    7730             :             }
    7731             :         }
    7732             :     }
    7733          45 :     else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
    7734             :     {
    7735          63 :         if (sqlite3_column_count(hSQLStmt) == 1 &&
    7736          18 :             sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
    7737             :         {
    7738          15 :             int ret = sqlite3_column_int(hSQLStmt, 0);
    7739             : 
    7740          15 :             sqlite3_finalize(hSQLStmt);
    7741             : 
    7742          15 :             return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
    7743          15 :                                                    ret);
    7744             :         }
    7745          33 :         else if (sqlite3_column_count(hSQLStmt) == 1 &&
    7746           3 :                  sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
    7747             :         {
    7748             :             const char *pszRet = reinterpret_cast<const char *>(
    7749           3 :                 sqlite3_column_text(hSQLStmt, 0));
    7750             : 
    7751             :             OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
    7752           3 :                 osSQLCommand.c_str() + 7, pszRet);
    7753             : 
    7754           3 :             sqlite3_finalize(hSQLStmt);
    7755             : 
    7756           3 :             return poRet;
    7757             :         }
    7758             :     }
    7759             : 
    7760             :     /* -------------------------------------------------------------------- */
    7761             :     /*      Create layer.                                                   */
    7762             :     /* -------------------------------------------------------------------- */
    7763             : 
    7764             :     auto poLayer = std::make_unique<OGRGeoPackageSelectLayer>(
    7765             :         this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
    7766        1542 :         bEmptyLayer);
    7767             : 
    7768         774 :     if (poSpatialFilter != nullptr &&
    7769           3 :         poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
    7770           3 :         poLayer->SetSpatialFilter(0, poSpatialFilter);
    7771             : 
    7772         771 :     return poLayer.release();
    7773             : }
    7774             : 
    7775             : /************************************************************************/
    7776             : /*                          ReleaseResultSet()                          */
    7777             : /************************************************************************/
    7778             : 
    7779         801 : void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
    7780             : 
    7781             : {
    7782         801 :     delete poLayer;
    7783         801 : }
    7784             : 
    7785             : /************************************************************************/
    7786             : /*                         HasExtensionsTable()                         */
    7787             : /************************************************************************/
    7788             : 
    7789        6363 : bool GDALGeoPackageDataset::HasExtensionsTable()
    7790             : {
    7791        6363 :     return SQLGetInteger(
    7792             :                hDB,
    7793             :                "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
    7794             :                "AND type IN ('table', 'view')",
    7795        6363 :                nullptr) == 1;
    7796             : }
    7797             : 
    7798             : /************************************************************************/
    7799             : /*                    CheckUnknownExtensions()                          */
    7800             : /************************************************************************/
    7801             : 
    7802        1443 : void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
    7803             : {
    7804        1443 :     if (!HasExtensionsTable())
    7805         195 :         return;
    7806             : 
    7807        1248 :     char *pszSQL = nullptr;
    7808        1248 :     if (!bCheckRasterTable)
    7809        1039 :         pszSQL = sqlite3_mprintf(
    7810             :             "SELECT extension_name, definition, scope FROM gpkg_extensions "
    7811             :             "WHERE (table_name IS NULL "
    7812             :             "AND extension_name IS NOT NULL "
    7813             :             "AND definition IS NOT NULL "
    7814             :             "AND scope IS NOT NULL "
    7815             :             "AND extension_name NOT IN ("
    7816             :             "'gdal_aspatial', "
    7817             :             "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
    7818             :             "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
    7819             :                                        // 17-066r1 finalization
    7820             :             "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
    7821             :             "'gpkg_metadata', "
    7822             :             "'gpkg_schema', "
    7823             :             "'gpkg_crs_wkt', "
    7824             :             "'gpkg_crs_wkt_1_1', "
    7825             :             "'related_tables', 'gpkg_related_tables')) "
    7826             : #ifdef WORKAROUND_SQLITE3_BUGS
    7827             :             "OR 0 "
    7828             : #endif
    7829             :             "LIMIT 1000");
    7830             :     else
    7831         209 :         pszSQL = sqlite3_mprintf(
    7832             :             "SELECT extension_name, definition, scope FROM gpkg_extensions "
    7833             :             "WHERE (lower(table_name) = lower('%q') "
    7834             :             "AND extension_name IS NOT NULL "
    7835             :             "AND definition IS NOT NULL "
    7836             :             "AND scope IS NOT NULL "
    7837             :             "AND extension_name NOT IN ("
    7838             :             "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
    7839             :             "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
    7840             :                                        // 17-066r1 finalization
    7841             :             "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
    7842             :             "'gpkg_metadata', "
    7843             :             "'gpkg_schema', "
    7844             :             "'gpkg_crs_wkt', "
    7845             :             "'gpkg_crs_wkt_1_1', "
    7846             :             "'related_tables', 'gpkg_related_tables')) "
    7847             : #ifdef WORKAROUND_SQLITE3_BUGS
    7848             :             "OR 0 "
    7849             : #endif
    7850             :             "LIMIT 1000",
    7851             :             m_osRasterTable.c_str());
    7852             : 
    7853        2496 :     auto oResultTable = SQLQuery(GetDB(), pszSQL);
    7854        1248 :     sqlite3_free(pszSQL);
    7855        1248 :     if (oResultTable && oResultTable->RowCount() > 0)
    7856             :     {
    7857          44 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    7858             :         {
    7859          22 :             const char *pszExtName = oResultTable->GetValue(0, i);
    7860          22 :             const char *pszDefinition = oResultTable->GetValue(1, i);
    7861          22 :             const char *pszScope = oResultTable->GetValue(2, i);
    7862          22 :             if (pszExtName == nullptr || pszDefinition == nullptr ||
    7863             :                 pszScope == nullptr)
    7864             :             {
    7865           0 :                 continue;
    7866             :             }
    7867             : 
    7868          22 :             if (EQUAL(pszExtName, "gpkg_webp"))
    7869             :             {
    7870          16 :                 if (GDALGetDriverByName("WEBP") == nullptr)
    7871             :                 {
    7872           1 :                     CPLError(
    7873             :                         CE_Warning, CPLE_AppDefined,
    7874             :                         "Table %s contains WEBP tiles, but GDAL configured "
    7875             :                         "without WEBP support. Data will be missing",
    7876             :                         m_osRasterTable.c_str());
    7877             :                 }
    7878          16 :                 m_eTF = GPKG_TF_WEBP;
    7879          16 :                 continue;
    7880             :             }
    7881           6 :             if (EQUAL(pszExtName, "gpkg_zoom_other"))
    7882             :             {
    7883           2 :                 m_bZoomOther = true;
    7884           2 :                 continue;
    7885             :             }
    7886             : 
    7887           4 :             if (GetUpdate() && EQUAL(pszScope, "write-only"))
    7888             :             {
    7889           1 :                 CPLError(
    7890             :                     CE_Warning, CPLE_AppDefined,
    7891             :                     "Database relies on the '%s' (%s) extension that should "
    7892             :                     "be implemented for safe write-support, but is not "
    7893             :                     "currently. "
    7894             :                     "Update of that database are strongly discouraged to avoid "
    7895             :                     "corruption.",
    7896             :                     pszExtName, pszDefinition);
    7897             :             }
    7898           3 :             else if (GetUpdate() && EQUAL(pszScope, "read-write"))
    7899             :             {
    7900           1 :                 CPLError(
    7901             :                     CE_Warning, CPLE_AppDefined,
    7902             :                     "Database relies on the '%s' (%s) extension that should "
    7903             :                     "be implemented in order to read/write it safely, but is "
    7904             :                     "not currently. "
    7905             :                     "Some data may be missing while reading that database, and "
    7906             :                     "updates are strongly discouraged.",
    7907             :                     pszExtName, pszDefinition);
    7908             :             }
    7909           2 :             else if (EQUAL(pszScope, "read-write") &&
    7910             :                      // None of the NGA extensions at
    7911             :                      // http://ngageoint.github.io/GeoPackage/docs/extensions/
    7912             :                      // affect read-only scenarios
    7913           1 :                      !STARTS_WITH(pszExtName, "nga_"))
    7914             :             {
    7915           1 :                 CPLError(
    7916             :                     CE_Warning, CPLE_AppDefined,
    7917             :                     "Database relies on the '%s' (%s) extension that should "
    7918             :                     "be implemented in order to read it safely, but is not "
    7919             :                     "currently. "
    7920             :                     "Some data may be missing while reading that database.",
    7921             :                     pszExtName, pszDefinition);
    7922             :             }
    7923             :         }
    7924             :     }
    7925             : }
    7926             : 
    7927             : /************************************************************************/
    7928             : /*                         HasGDALAspatialExtension()                       */
    7929             : /************************************************************************/
    7930             : 
    7931         986 : bool GDALGeoPackageDataset::HasGDALAspatialExtension()
    7932             : {
    7933         986 :     if (!HasExtensionsTable())
    7934          88 :         return false;
    7935             : 
    7936             :     auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
    7937             :                                       "WHERE (extension_name = 'gdal_aspatial' "
    7938             :                                       "AND table_name IS NULL "
    7939             :                                       "AND column_name IS NULL)"
    7940             : #ifdef WORKAROUND_SQLITE3_BUGS
    7941             :                                       " OR 0"
    7942             : #endif
    7943         898 :     );
    7944         898 :     bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
    7945         898 :     return bHasExtension;
    7946             : }
    7947             : 
    7948             : std::string
    7949         189 : GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName)
    7950             : {
    7951             :     char *pszSQL;
    7952         189 :     std::string osSQL;
    7953             :     /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
    7954             :      * Definition SQL  */
    7955         189 :     pszSQL = sqlite3_mprintf(
    7956             :         "CREATE TRIGGER \"%w_zoom_insert\" "
    7957             :         "BEFORE INSERT ON \"%w\" "
    7958             :         "FOR EACH ROW BEGIN "
    7959             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    7960             :         "constraint: zoom_level not specified for table in "
    7961             :         "gpkg_tile_matrix') "
    7962             :         "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
    7963             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
    7964             :         "END; "
    7965             :         "CREATE TRIGGER \"%w_zoom_update\" "
    7966             :         "BEFORE UPDATE OF zoom_level ON \"%w\" "
    7967             :         "FOR EACH ROW BEGIN "
    7968             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    7969             :         "constraint: zoom_level not specified for table in "
    7970             :         "gpkg_tile_matrix') "
    7971             :         "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
    7972             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
    7973             :         "END; "
    7974             :         "CREATE TRIGGER \"%w_tile_column_insert\" "
    7975             :         "BEFORE INSERT ON \"%w\" "
    7976             :         "FOR EACH ROW BEGIN "
    7977             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    7978             :         "constraint: tile_column cannot be < 0') "
    7979             :         "WHERE (NEW.tile_column < 0) ; "
    7980             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    7981             :         "constraint: tile_column must by < matrix_width specified for "
    7982             :         "table and zoom level in gpkg_tile_matrix') "
    7983             :         "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
    7984             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    7985             :         "zoom_level = NEW.zoom_level)); "
    7986             :         "END; "
    7987             :         "CREATE TRIGGER \"%w_tile_column_update\" "
    7988             :         "BEFORE UPDATE OF tile_column ON \"%w\" "
    7989             :         "FOR EACH ROW BEGIN "
    7990             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    7991             :         "constraint: tile_column cannot be < 0') "
    7992             :         "WHERE (NEW.tile_column < 0) ; "
    7993             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    7994             :         "constraint: tile_column must by < matrix_width specified for "
    7995             :         "table and zoom level in gpkg_tile_matrix') "
    7996             :         "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
    7997             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    7998             :         "zoom_level = NEW.zoom_level)); "
    7999             :         "END; "
    8000             :         "CREATE TRIGGER \"%w_tile_row_insert\" "
    8001             :         "BEFORE INSERT ON \"%w\" "
    8002             :         "FOR EACH ROW BEGIN "
    8003             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8004             :         "constraint: tile_row cannot be < 0') "
    8005             :         "WHERE (NEW.tile_row < 0) ; "
    8006             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8007             :         "constraint: tile_row must by < matrix_height specified for "
    8008             :         "table and zoom level in gpkg_tile_matrix') "
    8009             :         "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
    8010             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8011             :         "zoom_level = NEW.zoom_level)); "
    8012             :         "END; "
    8013             :         "CREATE TRIGGER \"%w_tile_row_update\" "
    8014             :         "BEFORE UPDATE OF tile_row ON \"%w\" "
    8015             :         "FOR EACH ROW BEGIN "
    8016             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8017             :         "constraint: tile_row cannot be < 0') "
    8018             :         "WHERE (NEW.tile_row < 0) ; "
    8019             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8020             :         "constraint: tile_row must by < matrix_height specified for "
    8021             :         "table and zoom level in gpkg_tile_matrix') "
    8022             :         "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
    8023             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8024             :         "zoom_level = NEW.zoom_level)); "
    8025             :         "END; ",
    8026             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8027             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8028             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8029             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8030             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8031             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8032             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8033             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8034             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8035             :         osTableName.c_str());
    8036         189 :     osSQL = pszSQL;
    8037         189 :     sqlite3_free(pszSQL);
    8038         189 :     return osSQL;
    8039             : }
    8040             : 
    8041             : /************************************************************************/
    8042             : /*                  CreateExtensionsTableIfNecessary()                  */
    8043             : /************************************************************************/
    8044             : 
    8045        1135 : OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
    8046             : {
    8047             :     /* Check if the table gpkg_extensions exists */
    8048        1135 :     if (HasExtensionsTable())
    8049         403 :         return OGRERR_NONE;
    8050             : 
    8051             :     /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
    8052             :     /* in a corresponding row in the gpkg_extensions table. The absence of a */
    8053             :     /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
    8054             :     /* SHALL both indicate the absence of extensions to a GeoPackage. */
    8055         732 :     const char *pszCreateGpkgExtensions =
    8056             :         "CREATE TABLE gpkg_extensions ("
    8057             :         "table_name TEXT,"
    8058             :         "column_name TEXT,"
    8059             :         "extension_name TEXT NOT NULL,"
    8060             :         "definition TEXT NOT NULL,"
    8061             :         "scope TEXT NOT NULL,"
    8062             :         "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
    8063             :         ")";
    8064             : 
    8065         732 :     return SQLCommand(hDB, pszCreateGpkgExtensions);
    8066             : }
    8067             : 
    8068             : /************************************************************************/
    8069             : /*                    OGR_GPKG_Intersects_Spatial_Filter()              */
    8070             : /************************************************************************/
    8071             : 
    8072       23135 : void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
    8073             :                                         sqlite3_value **argv)
    8074             : {
    8075       23135 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8076             :     {
    8077           0 :         sqlite3_result_int(pContext, 0);
    8078       23125 :         return;
    8079             :     }
    8080             : 
    8081             :     auto poLayer =
    8082       23135 :         static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
    8083             : 
    8084       23135 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8085             :     const GByte *pabyBLOB =
    8086       23135 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8087             : 
    8088             :     GPkgHeader sHeader;
    8089       46270 :     if (poLayer->m_bFilterIsEnvelope &&
    8090       23135 :         OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
    8091             :     {
    8092       23135 :         if (sHeader.bExtentHasXY)
    8093             :         {
    8094          95 :             OGREnvelope sEnvelope;
    8095          95 :             sEnvelope.MinX = sHeader.MinX;
    8096          95 :             sEnvelope.MinY = sHeader.MinY;
    8097          95 :             sEnvelope.MaxX = sHeader.MaxX;
    8098          95 :             sEnvelope.MaxY = sHeader.MaxY;
    8099          95 :             if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
    8100             :             {
    8101          31 :                 sqlite3_result_int(pContext, 1);
    8102          31 :                 return;
    8103             :             }
    8104             :         }
    8105             : 
    8106             :         // Check if at least one point falls into the layer filter envelope
    8107             :         // nHeaderLen is > 0 for GeoPackage geometries
    8108       46208 :         if (sHeader.nHeaderLen > 0 &&
    8109       23104 :             OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
    8110       23104 :                                         nBLOBLen - sHeader.nHeaderLen,
    8111       23104 :                                         poLayer->m_sFilterEnvelope))
    8112             :         {
    8113       23094 :             sqlite3_result_int(pContext, 1);
    8114       23094 :             return;
    8115             :         }
    8116             :     }
    8117             : 
    8118             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8119          10 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8120          10 :     if (poGeom == nullptr)
    8121             :     {
    8122             :         // Try also spatialite geometry blobs
    8123           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8124           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8125           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8126             :         {
    8127           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8128           0 :             sqlite3_result_int(pContext, 0);
    8129           0 :             return;
    8130             :         }
    8131           0 :         poGeom.reset(poGeomSpatialite);
    8132             :     }
    8133             : 
    8134          10 :     sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
    8135             : }
    8136             : 
    8137             : /************************************************************************/
    8138             : /*                      OGRGeoPackageSTMinX()                           */
    8139             : /************************************************************************/
    8140             : 
    8141      243198 : static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
    8142             :                                 sqlite3_value **argv)
    8143             : {
    8144             :     GPkgHeader sHeader;
    8145      243198 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8146             :     {
    8147           3 :         sqlite3_result_null(pContext);
    8148           3 :         return;
    8149             :     }
    8150      243195 :     sqlite3_result_double(pContext, sHeader.MinX);
    8151             : }
    8152             : 
    8153             : /************************************************************************/
    8154             : /*                      OGRGeoPackageSTMinY()                           */
    8155             : /************************************************************************/
    8156             : 
    8157      243196 : static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
    8158             :                                 sqlite3_value **argv)
    8159             : {
    8160             :     GPkgHeader sHeader;
    8161      243196 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8162             :     {
    8163           1 :         sqlite3_result_null(pContext);
    8164           1 :         return;
    8165             :     }
    8166      243195 :     sqlite3_result_double(pContext, sHeader.MinY);
    8167             : }
    8168             : 
    8169             : /************************************************************************/
    8170             : /*                      OGRGeoPackageSTMaxX()                           */
    8171             : /************************************************************************/
    8172             : 
    8173      243196 : static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
    8174             :                                 sqlite3_value **argv)
    8175             : {
    8176             :     GPkgHeader sHeader;
    8177      243196 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8178             :     {
    8179           1 :         sqlite3_result_null(pContext);
    8180           1 :         return;
    8181             :     }
    8182      243195 :     sqlite3_result_double(pContext, sHeader.MaxX);
    8183             : }
    8184             : 
    8185             : /************************************************************************/
    8186             : /*                      OGRGeoPackageSTMaxY()                           */
    8187             : /************************************************************************/
    8188             : 
    8189      243196 : static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
    8190             :                                 sqlite3_value **argv)
    8191             : {
    8192             :     GPkgHeader sHeader;
    8193      243196 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8194             :     {
    8195           1 :         sqlite3_result_null(pContext);
    8196           1 :         return;
    8197             :     }
    8198      243195 :     sqlite3_result_double(pContext, sHeader.MaxY);
    8199             : }
    8200             : 
    8201             : /************************************************************************/
    8202             : /*                     OGRGeoPackageSTIsEmpty()                         */
    8203             : /************************************************************************/
    8204             : 
    8205      244599 : static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
    8206             :                                    sqlite3_value **argv)
    8207             : {
    8208             :     GPkgHeader sHeader;
    8209      244599 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8210             :     {
    8211           2 :         sqlite3_result_null(pContext);
    8212           2 :         return;
    8213             :     }
    8214      244597 :     sqlite3_result_int(pContext, sHeader.bEmpty);
    8215             : }
    8216             : 
    8217             : /************************************************************************/
    8218             : /*                    OGRGeoPackageSTGeometryType()                     */
    8219             : /************************************************************************/
    8220             : 
    8221           7 : static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
    8222             :                                         sqlite3_value **argv)
    8223             : {
    8224             :     GPkgHeader sHeader;
    8225             : 
    8226           7 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8227             :     const GByte *pabyBLOB =
    8228           7 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8229             :     OGRwkbGeometryType eGeometryType;
    8230             : 
    8231          13 :     if (nBLOBLen < 8 ||
    8232           6 :         GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
    8233             :     {
    8234           2 :         if (OGRSQLiteGetSpatialiteGeometryHeader(
    8235             :                 pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
    8236           2 :                 nullptr, nullptr, nullptr) == OGRERR_NONE)
    8237             :         {
    8238           1 :             sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
    8239             :                                 SQLITE_TRANSIENT);
    8240           4 :             return;
    8241             :         }
    8242             :         else
    8243             :         {
    8244           1 :             sqlite3_result_null(pContext);
    8245           1 :             return;
    8246             :         }
    8247             :     }
    8248             : 
    8249           5 :     if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
    8250             :     {
    8251           2 :         sqlite3_result_null(pContext);
    8252           2 :         return;
    8253             :     }
    8254             : 
    8255           3 :     OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
    8256             :                                         wkbVariantIso, &eGeometryType);
    8257           3 :     if (err != OGRERR_NONE)
    8258           1 :         sqlite3_result_null(pContext);
    8259             :     else
    8260           2 :         sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
    8261             :                             SQLITE_TRANSIENT);
    8262             : }
    8263             : 
    8264             : /************************************************************************/
    8265             : /*                 OGRGeoPackageSTEnvelopesIntersects()                 */
    8266             : /************************************************************************/
    8267             : 
    8268         118 : static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
    8269             :                                                int argc, sqlite3_value **argv)
    8270             : {
    8271             :     GPkgHeader sHeader;
    8272         118 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8273             :     {
    8274           2 :         sqlite3_result_int(pContext, FALSE);
    8275         107 :         return;
    8276             :     }
    8277         116 :     const double dfMinX = sqlite3_value_double(argv[1]);
    8278         116 :     if (sHeader.MaxX < dfMinX)
    8279             :     {
    8280          93 :         sqlite3_result_int(pContext, FALSE);
    8281          93 :         return;
    8282             :     }
    8283          23 :     const double dfMinY = sqlite3_value_double(argv[2]);
    8284          23 :     if (sHeader.MaxY < dfMinY)
    8285             :     {
    8286          11 :         sqlite3_result_int(pContext, FALSE);
    8287          11 :         return;
    8288             :     }
    8289          12 :     const double dfMaxX = sqlite3_value_double(argv[3]);
    8290          12 :     if (sHeader.MinX > dfMaxX)
    8291             :     {
    8292           1 :         sqlite3_result_int(pContext, FALSE);
    8293           1 :         return;
    8294             :     }
    8295          11 :     const double dfMaxY = sqlite3_value_double(argv[4]);
    8296          11 :     sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
    8297             : }
    8298             : 
    8299             : /************************************************************************/
    8300             : /*              OGRGeoPackageSTEnvelopesIntersectsTwoParams()           */
    8301             : /************************************************************************/
    8302             : 
    8303             : static void
    8304           3 : OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
    8305             :                                             sqlite3_value **argv)
    8306             : {
    8307             :     GPkgHeader sHeader;
    8308           3 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
    8309             :     {
    8310           0 :         sqlite3_result_int(pContext, FALSE);
    8311           2 :         return;
    8312             :     }
    8313             :     GPkgHeader sHeader2;
    8314           3 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
    8315             :                                 1))
    8316             :     {
    8317           0 :         sqlite3_result_int(pContext, FALSE);
    8318           0 :         return;
    8319             :     }
    8320           3 :     if (sHeader.MaxX < sHeader2.MinX)
    8321             :     {
    8322           1 :         sqlite3_result_int(pContext, FALSE);
    8323           1 :         return;
    8324             :     }
    8325           2 :     if (sHeader.MaxY < sHeader2.MinY)
    8326             :     {
    8327           0 :         sqlite3_result_int(pContext, FALSE);
    8328           0 :         return;
    8329             :     }
    8330           2 :     if (sHeader.MinX > sHeader2.MaxX)
    8331             :     {
    8332           1 :         sqlite3_result_int(pContext, FALSE);
    8333           1 :         return;
    8334             :     }
    8335           1 :     sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
    8336             : }
    8337             : 
    8338             : /************************************************************************/
    8339             : /*                    OGRGeoPackageGPKGIsAssignable()                   */
    8340             : /************************************************************************/
    8341             : 
    8342           8 : static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
    8343             :                                           int /*argc*/, sqlite3_value **argv)
    8344             : {
    8345          15 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8346           7 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    8347             :     {
    8348           2 :         sqlite3_result_int(pContext, 0);
    8349           2 :         return;
    8350             :     }
    8351             : 
    8352             :     const char *pszExpected =
    8353           6 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8354             :     const char *pszActual =
    8355           6 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8356           6 :     int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
    8357             :                                             OGRFromOGCGeomType(pszExpected));
    8358           6 :     sqlite3_result_int(pContext, bIsAssignable);
    8359             : }
    8360             : 
    8361             : /************************************************************************/
    8362             : /*                     OGRGeoPackageSTSRID()                            */
    8363             : /************************************************************************/
    8364             : 
    8365          12 : static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
    8366             :                                 sqlite3_value **argv)
    8367             : {
    8368             :     GPkgHeader sHeader;
    8369          12 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8370             :     {
    8371           2 :         sqlite3_result_null(pContext);
    8372           2 :         return;
    8373             :     }
    8374          10 :     sqlite3_result_int(pContext, sHeader.iSrsId);
    8375             : }
    8376             : 
    8377             : /************************************************************************/
    8378             : /*                     OGRGeoPackageSetSRID()                           */
    8379             : /************************************************************************/
    8380             : 
    8381          28 : static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
    8382             :                                  sqlite3_value **argv)
    8383             : {
    8384          28 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8385             :     {
    8386           1 :         sqlite3_result_null(pContext);
    8387           1 :         return;
    8388             :     }
    8389          27 :     const int nDestSRID = sqlite3_value_int(argv[1]);
    8390             :     GPkgHeader sHeader;
    8391          27 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8392             :     const GByte *pabyBLOB =
    8393          27 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8394             : 
    8395          54 :     if (nBLOBLen < 8 ||
    8396          27 :         GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
    8397             :     {
    8398             :         // Try also spatialite geometry blobs
    8399           0 :         OGRGeometry *poGeom = nullptr;
    8400           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
    8401             :             OGRERR_NONE)
    8402             :         {
    8403           0 :             sqlite3_result_null(pContext);
    8404           0 :             return;
    8405             :         }
    8406           0 :         size_t nBLOBDestLen = 0;
    8407             :         GByte *pabyDestBLOB =
    8408           0 :             GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
    8409           0 :         if (!pabyDestBLOB)
    8410             :         {
    8411           0 :             sqlite3_result_null(pContext);
    8412           0 :             return;
    8413             :         }
    8414           0 :         sqlite3_result_blob(pContext, pabyDestBLOB,
    8415             :                             static_cast<int>(nBLOBDestLen), VSIFree);
    8416           0 :         return;
    8417             :     }
    8418             : 
    8419          27 :     GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
    8420          27 :     memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
    8421          27 :     int32_t nSRIDToSerialize = nDestSRID;
    8422          27 :     if (OGR_SWAP(sHeader.eByteOrder))
    8423           0 :         nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
    8424          27 :     memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
    8425          27 :     sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
    8426             : }
    8427             : 
    8428             : /************************************************************************/
    8429             : /*                   OGRGeoPackageSTMakeValid()                         */
    8430             : /************************************************************************/
    8431             : 
    8432           3 : static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
    8433             :                                      sqlite3_value **argv)
    8434             : {
    8435           3 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8436             :     {
    8437           2 :         sqlite3_result_null(pContext);
    8438           2 :         return;
    8439             :     }
    8440           1 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8441             :     const GByte *pabyBLOB =
    8442           1 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8443             : 
    8444             :     GPkgHeader sHeader;
    8445           1 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8446             :     {
    8447           0 :         sqlite3_result_null(pContext);
    8448           0 :         return;
    8449             :     }
    8450             : 
    8451             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8452           1 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8453           1 :     if (poGeom == nullptr)
    8454             :     {
    8455             :         // Try also spatialite geometry blobs
    8456           0 :         OGRGeometry *poGeomPtr = nullptr;
    8457           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
    8458             :             OGRERR_NONE)
    8459             :         {
    8460           0 :             sqlite3_result_null(pContext);
    8461           0 :             return;
    8462             :         }
    8463           0 :         poGeom.reset(poGeomPtr);
    8464             :     }
    8465           1 :     auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
    8466           1 :     if (poValid == nullptr)
    8467             :     {
    8468           0 :         sqlite3_result_null(pContext);
    8469           0 :         return;
    8470             :     }
    8471             : 
    8472           1 :     size_t nBLOBDestLen = 0;
    8473           1 :     GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
    8474             :                                               nullptr, &nBLOBDestLen);
    8475           1 :     if (!pabyDestBLOB)
    8476             :     {
    8477           0 :         sqlite3_result_null(pContext);
    8478           0 :         return;
    8479             :     }
    8480           1 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    8481             :                         VSIFree);
    8482             : }
    8483             : 
    8484             : /************************************************************************/
    8485             : /*                   OGRGeoPackageSTArea()                              */
    8486             : /************************************************************************/
    8487             : 
    8488          19 : static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
    8489             :                                 sqlite3_value **argv)
    8490             : {
    8491          19 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8492             :     {
    8493           1 :         sqlite3_result_null(pContext);
    8494          15 :         return;
    8495             :     }
    8496          18 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8497             :     const GByte *pabyBLOB =
    8498          18 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8499             : 
    8500             :     GPkgHeader sHeader;
    8501           0 :     std::unique_ptr<OGRGeometry> poGeom;
    8502          18 :     if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
    8503             :     {
    8504          16 :         if (sHeader.bEmpty)
    8505             :         {
    8506           3 :             sqlite3_result_double(pContext, 0);
    8507          13 :             return;
    8508             :         }
    8509          13 :         const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
    8510          13 :         size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
    8511             :         bool bNeedSwap;
    8512             :         uint32_t nType;
    8513          13 :         if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
    8514             :         {
    8515          13 :             if (nType == wkbPolygon || nType == wkbPolygon25D ||
    8516          11 :                 nType == wkbPolygon + 1000 ||  // wkbPolygonZ
    8517          10 :                 nType == wkbPolygonM || nType == wkbPolygonZM)
    8518             :             {
    8519             :                 double dfArea;
    8520           5 :                 if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
    8521             :                 {
    8522           5 :                     sqlite3_result_double(pContext, dfArea);
    8523           5 :                     return;
    8524           0 :                 }
    8525             :             }
    8526           8 :             else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
    8527           6 :                      nType == wkbMultiPolygon + 1000 ||  // wkbMultiPolygonZ
    8528           5 :                      nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
    8529             :             {
    8530             :                 double dfArea;
    8531           5 :                 if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
    8532             :                 {
    8533           5 :                     sqlite3_result_double(pContext, dfArea);
    8534           5 :                     return;
    8535             :                 }
    8536             :             }
    8537             :         }
    8538             : 
    8539             :         // For curve geometries, fallback to OGRGeometry methods
    8540           3 :         poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8541             :     }
    8542             :     else
    8543             :     {
    8544             :         // Try also spatialite geometry blobs
    8545           2 :         OGRGeometry *poGeomPtr = nullptr;
    8546           2 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
    8547             :             OGRERR_NONE)
    8548             :         {
    8549           1 :             sqlite3_result_null(pContext);
    8550           1 :             return;
    8551             :         }
    8552           1 :         poGeom.reset(poGeomPtr);
    8553             :     }
    8554           4 :     auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
    8555           4 :     if (poSurface == nullptr)
    8556             :     {
    8557           2 :         auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
    8558           2 :         if (poMultiSurface == nullptr)
    8559             :         {
    8560           1 :             sqlite3_result_double(pContext, 0);
    8561             :         }
    8562             :         else
    8563             :         {
    8564           1 :             sqlite3_result_double(pContext, poMultiSurface->get_Area());
    8565             :         }
    8566             :     }
    8567             :     else
    8568             :     {
    8569           2 :         sqlite3_result_double(pContext, poSurface->get_Area());
    8570             :     }
    8571             : }
    8572             : 
    8573             : /************************************************************************/
    8574             : /*                     OGRGeoPackageGeodesicArea()                      */
    8575             : /************************************************************************/
    8576             : 
    8577           5 : static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
    8578             :                                       sqlite3_value **argv)
    8579             : {
    8580           5 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8581             :     {
    8582           1 :         sqlite3_result_null(pContext);
    8583           3 :         return;
    8584             :     }
    8585           4 :     if (sqlite3_value_int(argv[1]) != 1)
    8586             :     {
    8587           2 :         CPLError(CE_Warning, CPLE_NotSupported,
    8588             :                  "ST_Area(geom, use_ellipsoid) is only supported for "
    8589             :                  "use_ellipsoid = 1");
    8590             :     }
    8591             : 
    8592           4 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8593             :     const GByte *pabyBLOB =
    8594           4 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8595             :     GPkgHeader sHeader;
    8596           4 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8597             :     {
    8598           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8599           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8600           1 :         return;
    8601             :     }
    8602             : 
    8603             :     GDALGeoPackageDataset *poDS =
    8604           3 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8605             : 
    8606             :     std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS(
    8607           3 :         poDS->GetSpatialRef(sHeader.iSrsId, true));
    8608           3 :     if (poSrcSRS == nullptr)
    8609             :     {
    8610           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    8611             :                  "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8612           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8613           1 :         return;
    8614             :     }
    8615             : 
    8616             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8617           2 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8618           2 :     if (poGeom == nullptr)
    8619             :     {
    8620             :         // Try also spatialite geometry blobs
    8621           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8622           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8623           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8624             :         {
    8625           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8626           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8627           0 :             return;
    8628             :         }
    8629           0 :         poGeom.reset(poGeomSpatialite);
    8630             :     }
    8631             : 
    8632           2 :     poGeom->assignSpatialReference(poSrcSRS.get());
    8633           2 :     sqlite3_result_double(
    8634             :         pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
    8635             : }
    8636             : 
    8637             : /************************************************************************/
    8638             : /*                   OGRGeoPackageLengthOrGeodesicLength()              */
    8639             : /************************************************************************/
    8640             : 
    8641           8 : static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext,
    8642             :                                                 int argc, sqlite3_value **argv)
    8643             : {
    8644           8 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8645             :     {
    8646           2 :         sqlite3_result_null(pContext);
    8647           5 :         return;
    8648             :     }
    8649           6 :     if (argc == 2 && sqlite3_value_int(argv[1]) != 1)
    8650             :     {
    8651           2 :         CPLError(CE_Warning, CPLE_NotSupported,
    8652             :                  "ST_Length(geom, use_ellipsoid) is only supported for "
    8653             :                  "use_ellipsoid = 1");
    8654             :     }
    8655             : 
    8656           6 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8657             :     const GByte *pabyBLOB =
    8658           6 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8659             :     GPkgHeader sHeader;
    8660           6 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8661             :     {
    8662           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8663           2 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8664           2 :         return;
    8665             :     }
    8666             : 
    8667             :     GDALGeoPackageDataset *poDS =
    8668           4 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8669             : 
    8670           0 :     std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS;
    8671           4 :     if (argc == 2)
    8672             :     {
    8673           3 :         poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
    8674           3 :         if (!poSrcSRS)
    8675             :         {
    8676           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    8677             :                      "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8678           1 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8679           1 :             return;
    8680             :         }
    8681             :     }
    8682             : 
    8683             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8684           3 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8685           3 :     if (poGeom == nullptr)
    8686             :     {
    8687             :         // Try also spatialite geometry blobs
    8688           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8689           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8690           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8691             :         {
    8692           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8693           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8694           0 :             return;
    8695             :         }
    8696           0 :         poGeom.reset(poGeomSpatialite);
    8697             :     }
    8698             : 
    8699           3 :     if (argc == 2)
    8700           2 :         poGeom->assignSpatialReference(poSrcSRS.get());
    8701             : 
    8702           6 :     sqlite3_result_double(
    8703             :         pContext,
    8704           1 :         argc == 1 ? OGR_G_Length(OGRGeometry::ToHandle(poGeom.get()))
    8705           2 :                   : OGR_G_GeodesicLength(OGRGeometry::ToHandle(poGeom.get())));
    8706             : }
    8707             : 
    8708             : /************************************************************************/
    8709             : /*                      OGRGeoPackageTransform()                        */
    8710             : /************************************************************************/
    8711             : 
    8712             : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
    8713             :                             sqlite3_value **argv);
    8714             : 
    8715          32 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
    8716             :                             sqlite3_value **argv)
    8717             : {
    8718          63 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
    8719          31 :         sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
    8720             :     {
    8721           2 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8722          32 :         return;
    8723             :     }
    8724             : 
    8725          30 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8726             :     const GByte *pabyBLOB =
    8727          30 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8728             :     GPkgHeader sHeader;
    8729          30 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8730             :     {
    8731           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8732           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8733           1 :         return;
    8734             :     }
    8735             : 
    8736          29 :     const int nDestSRID = sqlite3_value_int(argv[1]);
    8737          29 :     if (sHeader.iSrsId == nDestSRID)
    8738             :     {
    8739             :         // Return blob unmodified
    8740           3 :         sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
    8741           3 :         return;
    8742             :     }
    8743             : 
    8744             :     GDALGeoPackageDataset *poDS =
    8745          26 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8746             : 
    8747             :     // Try to get the cached coordinate transformation
    8748             :     OGRCoordinateTransformation *poCT;
    8749          26 :     if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
    8750          20 :         poDS->m_nLastCachedCTDstSRId == nDestSRID)
    8751             :     {
    8752          20 :         poCT = poDS->m_poLastCachedCT.get();
    8753             :     }
    8754             :     else
    8755             :     {
    8756             :         std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
    8757           6 :             poSrcSRS(poDS->GetSpatialRef(sHeader.iSrsId, true));
    8758           6 :         if (poSrcSRS == nullptr)
    8759             :         {
    8760           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    8761             :                      "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8762           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8763           0 :             return;
    8764             :         }
    8765             : 
    8766             :         std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
    8767           6 :             poDstSRS(poDS->GetSpatialRef(nDestSRID, true));
    8768           6 :         if (poDstSRS == nullptr)
    8769             :         {
    8770           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
    8771             :                      nDestSRID);
    8772           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8773           0 :             return;
    8774             :         }
    8775             :         poCT =
    8776           6 :             OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get());
    8777           6 :         if (poCT == nullptr)
    8778             :         {
    8779           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8780           0 :             return;
    8781             :         }
    8782             : 
    8783             :         // Cache coordinate transformation for potential later reuse
    8784           6 :         poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
    8785           6 :         poDS->m_nLastCachedCTDstSRId = nDestSRID;
    8786           6 :         poDS->m_poLastCachedCT.reset(poCT);
    8787           6 :         poCT = poDS->m_poLastCachedCT.get();
    8788             :     }
    8789             : 
    8790          26 :     if (sHeader.nHeaderLen >= 8)
    8791             :     {
    8792          26 :         std::vector<GByte> &abyNewBLOB = poDS->m_abyWKBTransformCache;
    8793          26 :         abyNewBLOB.resize(nBLOBLen);
    8794          26 :         memcpy(abyNewBLOB.data(), pabyBLOB, nBLOBLen);
    8795             : 
    8796          26 :         OGREnvelope3D oEnv3d;
    8797          26 :         if (!OGRWKBTransform(abyNewBLOB.data() + sHeader.nHeaderLen,
    8798          26 :                              nBLOBLen - sHeader.nHeaderLen, poCT,
    8799          78 :                              poDS->m_oWKBTransformCache, oEnv3d) ||
    8800          26 :             !GPkgUpdateHeader(abyNewBLOB.data(), nBLOBLen, nDestSRID,
    8801             :                               oEnv3d.MinX, oEnv3d.MaxX, oEnv3d.MinY,
    8802             :                               oEnv3d.MaxY, oEnv3d.MinZ, oEnv3d.MaxZ))
    8803             :         {
    8804           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8805           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8806           0 :             return;
    8807             :         }
    8808             : 
    8809          26 :         sqlite3_result_blob(pContext, abyNewBLOB.data(), nBLOBLen,
    8810             :                             SQLITE_TRANSIENT);
    8811          26 :         return;
    8812             :     }
    8813             : 
    8814             :     // Try also spatialite geometry blobs
    8815           0 :     OGRGeometry *poGeomSpatialite = nullptr;
    8816           0 :     if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8817           0 :                                           &poGeomSpatialite) != OGRERR_NONE)
    8818             :     {
    8819           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8820           0 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8821           0 :         return;
    8822             :     }
    8823           0 :     auto poGeom = std::unique_ptr<OGRGeometry>(poGeomSpatialite);
    8824             : 
    8825           0 :     if (poGeom->transform(poCT) != OGRERR_NONE)
    8826             :     {
    8827           0 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8828           0 :         return;
    8829             :     }
    8830             : 
    8831           0 :     size_t nBLOBDestLen = 0;
    8832             :     GByte *pabyDestBLOB =
    8833           0 :         GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
    8834           0 :     if (!pabyDestBLOB)
    8835             :     {
    8836           0 :         sqlite3_result_null(pContext);
    8837           0 :         return;
    8838             :     }
    8839           0 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    8840             :                         VSIFree);
    8841             : }
    8842             : 
    8843             : /************************************************************************/
    8844             : /*                      OGRGeoPackageSridFromAuthCRS()                  */
    8845             : /************************************************************************/
    8846             : 
    8847           4 : static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
    8848             :                                          int /*argc*/, sqlite3_value **argv)
    8849             : {
    8850           7 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8851           3 :         sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
    8852             :     {
    8853           2 :         sqlite3_result_int(pContext, -1);
    8854           2 :         return;
    8855             :     }
    8856             : 
    8857             :     GDALGeoPackageDataset *poDS =
    8858           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8859             : 
    8860           2 :     char *pszSQL = sqlite3_mprintf(
    8861             :         "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
    8862             :         "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
    8863           2 :         sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
    8864           2 :     OGRErr err = OGRERR_NONE;
    8865           2 :     int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
    8866           2 :     sqlite3_free(pszSQL);
    8867           2 :     if (err != OGRERR_NONE)
    8868           1 :         nSRSId = -1;
    8869           2 :     sqlite3_result_int(pContext, nSRSId);
    8870             : }
    8871             : 
    8872             : /************************************************************************/
    8873             : /*                    OGRGeoPackageImportFromEPSG()                     */
    8874             : /************************************************************************/
    8875             : 
    8876           4 : static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
    8877             :                                         sqlite3_value **argv)
    8878             : {
    8879           4 :     if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
    8880             :     {
    8881           1 :         sqlite3_result_int(pContext, -1);
    8882           2 :         return;
    8883             :     }
    8884             : 
    8885             :     GDALGeoPackageDataset *poDS =
    8886           3 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8887           3 :     OGRSpatialReference oSRS;
    8888           3 :     if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
    8889             :     {
    8890           1 :         sqlite3_result_int(pContext, -1);
    8891           1 :         return;
    8892             :     }
    8893             : 
    8894           2 :     sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
    8895             : }
    8896             : 
    8897             : /************************************************************************/
    8898             : /*               OGRGeoPackageRegisterGeometryExtension()               */
    8899             : /************************************************************************/
    8900             : 
    8901           1 : static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
    8902             :                                                    int /*argc*/,
    8903             :                                                    sqlite3_value **argv)
    8904             : {
    8905           1 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8906           2 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
    8907           1 :         sqlite3_value_type(argv[2]) != SQLITE_TEXT)
    8908             :     {
    8909           0 :         sqlite3_result_int(pContext, 0);
    8910           0 :         return;
    8911             :     }
    8912             : 
    8913             :     const char *pszTableName =
    8914           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8915             :     const char *pszGeomName =
    8916           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8917             :     const char *pszGeomType =
    8918           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
    8919             : 
    8920             :     GDALGeoPackageDataset *poDS =
    8921           1 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8922             : 
    8923           1 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    8924           1 :         poDS->GetLayerByName(pszTableName));
    8925           1 :     if (poLyr == nullptr)
    8926             :     {
    8927           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    8928           0 :         sqlite3_result_int(pContext, 0);
    8929           0 :         return;
    8930             :     }
    8931           1 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    8932             :     {
    8933           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    8934           0 :         sqlite3_result_int(pContext, 0);
    8935           0 :         return;
    8936             :     }
    8937           1 :     const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
    8938           1 :     if (eGeomType == wkbUnknown)
    8939             :     {
    8940           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
    8941           0 :         sqlite3_result_int(pContext, 0);
    8942           0 :         return;
    8943             :     }
    8944             : 
    8945           1 :     sqlite3_result_int(
    8946             :         pContext,
    8947           1 :         static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
    8948             : }
    8949             : 
    8950             : /************************************************************************/
    8951             : /*                  OGRGeoPackageCreateSpatialIndex()                   */
    8952             : /************************************************************************/
    8953             : 
    8954          14 : static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
    8955             :                                             int /*argc*/, sqlite3_value **argv)
    8956             : {
    8957          27 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8958          13 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    8959             :     {
    8960           2 :         sqlite3_result_int(pContext, 0);
    8961           2 :         return;
    8962             :     }
    8963             : 
    8964             :     const char *pszTableName =
    8965          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8966             :     const char *pszGeomName =
    8967          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8968             :     GDALGeoPackageDataset *poDS =
    8969          12 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8970             : 
    8971          12 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    8972          12 :         poDS->GetLayerByName(pszTableName));
    8973          12 :     if (poLyr == nullptr)
    8974             :     {
    8975           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    8976           1 :         sqlite3_result_int(pContext, 0);
    8977           1 :         return;
    8978             :     }
    8979          11 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    8980             :     {
    8981           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    8982           1 :         sqlite3_result_int(pContext, 0);
    8983           1 :         return;
    8984             :     }
    8985             : 
    8986          10 :     sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
    8987             : }
    8988             : 
    8989             : /************************************************************************/
    8990             : /*                  OGRGeoPackageDisableSpatialIndex()                  */
    8991             : /************************************************************************/
    8992             : 
    8993          12 : static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
    8994             :                                              int /*argc*/, sqlite3_value **argv)
    8995             : {
    8996          23 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8997          11 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    8998             :     {
    8999           2 :         sqlite3_result_int(pContext, 0);
    9000           2 :         return;
    9001             :     }
    9002             : 
    9003             :     const char *pszTableName =
    9004          10 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9005             :     const char *pszGeomName =
    9006          10 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9007             :     GDALGeoPackageDataset *poDS =
    9008          10 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9009             : 
    9010          10 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9011          10 :         poDS->GetLayerByName(pszTableName));
    9012          10 :     if (poLyr == nullptr)
    9013             :     {
    9014           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9015           1 :         sqlite3_result_int(pContext, 0);
    9016           1 :         return;
    9017             :     }
    9018           9 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9019             :     {
    9020           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9021           1 :         sqlite3_result_int(pContext, 0);
    9022           1 :         return;
    9023             :     }
    9024             : 
    9025           8 :     sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
    9026             : }
    9027             : 
    9028             : /************************************************************************/
    9029             : /*                  OGRGeoPackageHasSpatialIndex()                      */
    9030             : /************************************************************************/
    9031             : 
    9032          29 : static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
    9033             :                                          int /*argc*/, sqlite3_value **argv)
    9034             : {
    9035          57 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9036          28 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9037             :     {
    9038           2 :         sqlite3_result_int(pContext, 0);
    9039           2 :         return;
    9040             :     }
    9041             : 
    9042             :     const char *pszTableName =
    9043          27 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9044             :     const char *pszGeomName =
    9045          27 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9046             :     GDALGeoPackageDataset *poDS =
    9047          27 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9048             : 
    9049          27 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9050          27 :         poDS->GetLayerByName(pszTableName));
    9051          27 :     if (poLyr == nullptr)
    9052             :     {
    9053           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9054           1 :         sqlite3_result_int(pContext, 0);
    9055           1 :         return;
    9056             :     }
    9057          26 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9058             :     {
    9059           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9060           1 :         sqlite3_result_int(pContext, 0);
    9061           1 :         return;
    9062             :     }
    9063             : 
    9064          25 :     poLyr->RunDeferredCreationIfNecessary();
    9065          25 :     poLyr->CreateSpatialIndexIfNecessary();
    9066             : 
    9067          25 :     sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
    9068             : }
    9069             : 
    9070             : /************************************************************************/
    9071             : /*                       GPKG_hstore_get_value()                        */
    9072             : /************************************************************************/
    9073             : 
    9074           4 : static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
    9075             :                                   sqlite3_value **argv)
    9076             : {
    9077           7 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9078           3 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9079             :     {
    9080           2 :         sqlite3_result_null(pContext);
    9081           2 :         return;
    9082             :     }
    9083             : 
    9084             :     const char *pszHStore =
    9085           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9086             :     const char *pszSearchedKey =
    9087           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9088           2 :     char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
    9089           2 :     if (pszValue != nullptr)
    9090           1 :         sqlite3_result_text(pContext, pszValue, -1, CPLFree);
    9091             :     else
    9092           1 :         sqlite3_result_null(pContext);
    9093             : }
    9094             : 
    9095             : /************************************************************************/
    9096             : /*                      GPKG_GDAL_GetMemFileFromBlob()                  */
    9097             : /************************************************************************/
    9098             : 
    9099         105 : static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
    9100             : {
    9101         105 :     int nBytes = sqlite3_value_bytes(argv[0]);
    9102             :     const GByte *pabyBLOB =
    9103         105 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    9104             :     const CPLString osMemFileName(
    9105         105 :         VSIMemGenerateHiddenFilename("GPKG_GDAL_GetMemFileFromBlob"));
    9106         105 :     VSILFILE *fp = VSIFileFromMemBuffer(
    9107             :         osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
    9108         105 :     VSIFCloseL(fp);
    9109         105 :     return osMemFileName;
    9110             : }
    9111             : 
    9112             : /************************************************************************/
    9113             : /*                       GPKG_GDAL_GetMimeType()                        */
    9114             : /************************************************************************/
    9115             : 
    9116          35 : static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
    9117             :                                   sqlite3_value **argv)
    9118             : {
    9119          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9120             :     {
    9121           0 :         sqlite3_result_null(pContext);
    9122           0 :         return;
    9123             :     }
    9124             : 
    9125          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9126             :     GDALDriver *poDriver =
    9127          35 :         GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
    9128          35 :     if (poDriver != nullptr)
    9129             :     {
    9130          35 :         const char *pszRes = nullptr;
    9131          35 :         if (EQUAL(poDriver->GetDescription(), "PNG"))
    9132          23 :             pszRes = "image/png";
    9133          12 :         else if (EQUAL(poDriver->GetDescription(), "JPEG"))
    9134           6 :             pszRes = "image/jpeg";
    9135           6 :         else if (EQUAL(poDriver->GetDescription(), "WEBP"))
    9136           6 :             pszRes = "image/x-webp";
    9137           0 :         else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
    9138           0 :             pszRes = "image/tiff";
    9139             :         else
    9140           0 :             pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
    9141          35 :         sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
    9142             :     }
    9143             :     else
    9144           0 :         sqlite3_result_null(pContext);
    9145          35 :     VSIUnlink(osMemFileName);
    9146             : }
    9147             : 
    9148             : /************************************************************************/
    9149             : /*                       GPKG_GDAL_GetBandCount()                       */
    9150             : /************************************************************************/
    9151             : 
    9152          35 : static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
    9153             :                                    sqlite3_value **argv)
    9154             : {
    9155          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9156             :     {
    9157           0 :         sqlite3_result_null(pContext);
    9158           0 :         return;
    9159             :     }
    9160             : 
    9161          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9162             :     auto poDS = std::unique_ptr<GDALDataset>(
    9163             :         GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    9164          70 :                           nullptr, nullptr, nullptr));
    9165          35 :     if (poDS != nullptr)
    9166             :     {
    9167          35 :         sqlite3_result_int(pContext, poDS->GetRasterCount());
    9168             :     }
    9169             :     else
    9170           0 :         sqlite3_result_null(pContext);
    9171          35 :     VSIUnlink(osMemFileName);
    9172             : }
    9173             : 
    9174             : /************************************************************************/
    9175             : /*                       GPKG_GDAL_HasColorTable()                      */
    9176             : /************************************************************************/
    9177             : 
    9178          35 : static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
    9179             :                                     sqlite3_value **argv)
    9180             : {
    9181          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9182             :     {
    9183           0 :         sqlite3_result_null(pContext);
    9184           0 :         return;
    9185             :     }
    9186             : 
    9187          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9188             :     auto poDS = std::unique_ptr<GDALDataset>(
    9189             :         GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    9190          70 :                           nullptr, nullptr, nullptr));
    9191          35 :     if (poDS != nullptr)
    9192             :     {
    9193          35 :         sqlite3_result_int(
    9194          46 :             pContext, poDS->GetRasterCount() == 1 &&
    9195          11 :                           poDS->GetRasterBand(1)->GetColorTable() != nullptr);
    9196             :     }
    9197             :     else
    9198           0 :         sqlite3_result_null(pContext);
    9199          35 :     VSIUnlink(osMemFileName);
    9200             : }
    9201             : 
    9202             : /************************************************************************/
    9203             : /*                      GetRasterLayerDataset()                         */
    9204             : /************************************************************************/
    9205             : 
    9206             : GDALDataset *
    9207          12 : GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
    9208             : {
    9209          12 :     const auto oIter = m_oCachedRasterDS.find(pszLayerName);
    9210          12 :     if (oIter != m_oCachedRasterDS.end())
    9211          10 :         return oIter->second.get();
    9212             : 
    9213             :     auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
    9214           4 :         (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
    9215           4 :         GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
    9216           2 :     if (!poDS)
    9217             :     {
    9218           0 :         return nullptr;
    9219             :     }
    9220           2 :     m_oCachedRasterDS[pszLayerName] = std::move(poDS);
    9221           2 :     return m_oCachedRasterDS[pszLayerName].get();
    9222             : }
    9223             : 
    9224             : /************************************************************************/
    9225             : /*                   GPKG_gdal_get_layer_pixel_value()                  */
    9226             : /************************************************************************/
    9227             : 
    9228             : // NOTE: keep in sync implementations in ogrsqlitesqlfunctionscommon.cpp
    9229             : // and ogrgeopackagedatasource.cpp
    9230          13 : static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext, int argc,
    9231             :                                             sqlite3_value **argv)
    9232             : {
    9233          13 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
    9234             :     {
    9235           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    9236             :                  "Invalid arguments to gdal_get_layer_pixel_value()");
    9237           1 :         sqlite3_result_null(pContext);
    9238           1 :         return;
    9239             :     }
    9240             : 
    9241             :     const char *pszLayerName =
    9242          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9243             : 
    9244             :     GDALGeoPackageDataset *poGlobalDS =
    9245          12 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9246          12 :     auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
    9247          12 :     if (!poDS)
    9248             :     {
    9249           0 :         sqlite3_result_null(pContext);
    9250           0 :         return;
    9251             :     }
    9252             : 
    9253          12 :     OGRSQLite_gdal_get_pixel_value_common("gdal_get_layer_pixel_value",
    9254             :                                           pContext, argc, argv, poDS);
    9255             : }
    9256             : 
    9257             : /************************************************************************/
    9258             : /*                       GPKG_ogr_layer_Extent()                        */
    9259             : /************************************************************************/
    9260             : 
    9261           3 : static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
    9262             :                                   sqlite3_value **argv)
    9263             : {
    9264           3 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
    9265             :     {
    9266           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
    9267             :                  "ogr_layer_Extent");
    9268           1 :         sqlite3_result_null(pContext);
    9269           2 :         return;
    9270             :     }
    9271             : 
    9272             :     const char *pszLayerName =
    9273           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9274             :     GDALGeoPackageDataset *poDS =
    9275           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9276           2 :     OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
    9277           2 :     if (!poLayer)
    9278             :     {
    9279           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
    9280             :                  "ogr_layer_Extent");
    9281           1 :         sqlite3_result_null(pContext);
    9282           1 :         return;
    9283             :     }
    9284             : 
    9285           1 :     if (poLayer->GetGeomType() == wkbNone)
    9286             :     {
    9287           0 :         sqlite3_result_null(pContext);
    9288           0 :         return;
    9289             :     }
    9290             : 
    9291           1 :     OGREnvelope sExtent;
    9292           1 :     if (poLayer->GetExtent(&sExtent) != OGRERR_NONE)
    9293             :     {
    9294           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
    9295             :                  "ogr_layer_Extent");
    9296           0 :         sqlite3_result_null(pContext);
    9297           0 :         return;
    9298             :     }
    9299             : 
    9300           1 :     OGRPolygon oPoly;
    9301           1 :     auto poRing = std::make_unique<OGRLinearRing>();
    9302           1 :     poRing->addPoint(sExtent.MinX, sExtent.MinY);
    9303           1 :     poRing->addPoint(sExtent.MaxX, sExtent.MinY);
    9304           1 :     poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
    9305           1 :     poRing->addPoint(sExtent.MinX, sExtent.MaxY);
    9306           1 :     poRing->addPoint(sExtent.MinX, sExtent.MinY);
    9307           1 :     oPoly.addRing(std::move(poRing));
    9308             : 
    9309           1 :     const auto poSRS = poLayer->GetSpatialRef();
    9310           1 :     const int nSRID = poDS->GetSrsId(poSRS);
    9311           1 :     size_t nBLOBDestLen = 0;
    9312             :     GByte *pabyDestBLOB =
    9313           1 :         GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
    9314           1 :     if (!pabyDestBLOB)
    9315             :     {
    9316           0 :         sqlite3_result_null(pContext);
    9317           0 :         return;
    9318             :     }
    9319           1 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    9320             :                         VSIFree);
    9321             : }
    9322             : 
    9323             : /************************************************************************/
    9324             : /*                      InstallSQLFunctions()                           */
    9325             : /************************************************************************/
    9326             : 
    9327             : #ifndef SQLITE_DETERMINISTIC
    9328             : #define SQLITE_DETERMINISTIC 0
    9329             : #endif
    9330             : 
    9331             : #ifndef SQLITE_INNOCUOUS
    9332             : #define SQLITE_INNOCUOUS 0
    9333             : #endif
    9334             : 
    9335             : #ifndef UTF8_INNOCUOUS
    9336             : #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
    9337             : #endif
    9338             : 
    9339        2020 : void GDALGeoPackageDataset::InstallSQLFunctions()
    9340             : {
    9341        2020 :     InitSpatialite();
    9342             : 
    9343             :     // Enable SpatiaLite 4.3 "amphibious" mode, i.e. that SpatiaLite functions
    9344             :     // that take geometries will accept GPKG encoded geometries without
    9345             :     // explicit conversion.
    9346             :     // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
    9347             :     // error.
    9348        2020 :     sqlite3_exec(hDB, "SELECT EnableGpkgAmphibiousMode()", nullptr, nullptr,
    9349             :                  nullptr);
    9350             : 
    9351             :     /* Used by RTree Spatial Index Extension */
    9352        2020 :     sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
    9353             :                             OGRGeoPackageSTMinX, nullptr, nullptr);
    9354        2020 :     sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
    9355             :                             OGRGeoPackageSTMinY, nullptr, nullptr);
    9356        2020 :     sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
    9357             :                             OGRGeoPackageSTMaxX, nullptr, nullptr);
    9358        2020 :     sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
    9359             :                             OGRGeoPackageSTMaxY, nullptr, nullptr);
    9360        2020 :     sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
    9361             :                             OGRGeoPackageSTIsEmpty, nullptr, nullptr);
    9362             : 
    9363             :     /* Used by Geometry Type Triggers Extension */
    9364        2020 :     sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
    9365             :                             OGRGeoPackageSTGeometryType, nullptr, nullptr);
    9366        2020 :     sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
    9367             :                             nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
    9368             :                             nullptr);
    9369             : 
    9370             :     /* Used by Geometry SRS ID Triggers Extension */
    9371        2020 :     sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
    9372             :                             OGRGeoPackageSTSRID, nullptr, nullptr);
    9373             : 
    9374             :     /* Spatialite-like functions */
    9375        2020 :     sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
    9376             :                             OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
    9377        2020 :     sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
    9378             :                             OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
    9379        2020 :     sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
    9380             :                             OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
    9381             : 
    9382             :     // HSTORE functions
    9383        2020 :     sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
    9384             :                             GPKG_hstore_get_value, nullptr, nullptr);
    9385             : 
    9386             :     // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
    9387        2020 :     sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
    9388             :                             OGRGeoPackageTransform, nullptr, nullptr);
    9389        2020 :     sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
    9390             :                             OGRGeoPackageTransform, nullptr, nullptr);
    9391        2020 :     sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
    9392             :                             OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
    9393             : 
    9394        2020 :     sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
    9395             :                             OGRGeoPackageSTEnvelopesIntersectsTwoParams,
    9396             :                             nullptr, nullptr);
    9397        2020 :     sqlite3_create_function(
    9398             :         hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
    9399             :         OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
    9400             : 
    9401        2020 :     sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
    9402             :                             OGRGeoPackageSTEnvelopesIntersects, nullptr,
    9403             :                             nullptr);
    9404        2020 :     sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
    9405             :                             nullptr, OGRGeoPackageSTEnvelopesIntersects,
    9406             :                             nullptr, nullptr);
    9407             : 
    9408             :     // Implementation that directly hacks the GeoPackage geometry blob header
    9409        2020 :     sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
    9410             :                             OGRGeoPackageSetSRID, nullptr, nullptr);
    9411             : 
    9412             :     // GDAL specific function
    9413        2020 :     sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
    9414             :                             OGRGeoPackageImportFromEPSG, nullptr, nullptr);
    9415             : 
    9416             :     // May be used by ogrmerge.py
    9417        2020 :     sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
    9418             :                             this, OGRGeoPackageRegisterGeometryExtension,
    9419             :                             nullptr, nullptr);
    9420             : 
    9421        2020 :     if (OGRGeometryFactory::haveGEOS())
    9422             :     {
    9423        2020 :         sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
    9424             :                                 OGRGeoPackageSTMakeValid, nullptr, nullptr);
    9425             :     }
    9426             : 
    9427        2020 :     sqlite3_create_function(hDB, "ST_Length", 1, UTF8_INNOCUOUS, nullptr,
    9428             :                             OGRGeoPackageLengthOrGeodesicLength, nullptr,
    9429             :                             nullptr);
    9430        2020 :     sqlite3_create_function(hDB, "ST_Length", 2, UTF8_INNOCUOUS, this,
    9431             :                             OGRGeoPackageLengthOrGeodesicLength, nullptr,
    9432             :                             nullptr);
    9433             : 
    9434        2020 :     sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
    9435             :                             OGRGeoPackageSTArea, nullptr, nullptr);
    9436        2020 :     sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
    9437             :                             OGRGeoPackageGeodesicArea, nullptr, nullptr);
    9438             : 
    9439             :     // Debug functions
    9440        2020 :     if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
    9441             :     {
    9442         423 :         sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
    9443             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9444             :                                 GPKG_GDAL_GetMimeType, nullptr, nullptr);
    9445         423 :         sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
    9446             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9447             :                                 GPKG_GDAL_GetBandCount, nullptr, nullptr);
    9448         423 :         sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
    9449             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9450             :                                 GPKG_GDAL_HasColorTable, nullptr, nullptr);
    9451             :     }
    9452             : 
    9453        2020 :     sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
    9454             :                             this, GPKG_gdal_get_layer_pixel_value, nullptr,
    9455             :                             nullptr);
    9456        2020 :     sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 6, SQLITE_UTF8,
    9457             :                             this, GPKG_gdal_get_layer_pixel_value, nullptr,
    9458             :                             nullptr);
    9459             : 
    9460             :     // Function from VirtualOGR
    9461        2020 :     sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
    9462             :                             GPKG_ogr_layer_Extent, nullptr, nullptr);
    9463             : 
    9464        2020 :     m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
    9465        2020 : }
    9466             : 
    9467             : /************************************************************************/
    9468             : /*                         OpenOrCreateDB()                             */
    9469             : /************************************************************************/
    9470             : 
    9471        2023 : bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
    9472             : {
    9473        2023 :     const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
    9474             :         flags, /*bRegisterOGR2SQLiteExtensions=*/false,
    9475             :         /*bLoadExtensions=*/true);
    9476        2023 :     if (!bSuccess)
    9477           8 :         return false;
    9478             : 
    9479             :     // Turning on recursive_triggers is needed so that DELETE triggers fire
    9480             :     // in a INSERT OR REPLACE statement. In particular this is needed to
    9481             :     // make sure gpkg_ogr_contents.feature_count is properly updated.
    9482        2015 :     SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
    9483             : 
    9484        2015 :     InstallSQLFunctions();
    9485             : 
    9486             :     const char *pszSqlitePragma =
    9487        2015 :         CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
    9488        2015 :     OGRErr eErr = OGRERR_NONE;
    9489           6 :     if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
    9490             :         // Older sqlite versions don't have this pragma
    9491        4036 :         SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
    9492        2015 :         eErr == OGRERR_NONE)
    9493             :     {
    9494        2015 :         bool bNeedsTrustedSchema = false;
    9495             : 
    9496             :         // Current SQLite versions require PRAGMA trusted_schema = 1 to be
    9497             :         // able to use the RTree from triggers, which is only needed when
    9498             :         // modifying the RTree.
    9499        4975 :         if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
    9500        3085 :              (flags & SQLITE_OPEN_CREATE) != 0) &&
    9501        1070 :             OGRSQLiteRTreeRequiresTrustedSchemaOn())
    9502             :         {
    9503        1070 :             bNeedsTrustedSchema = true;
    9504             :         }
    9505             : 
    9506             : #ifdef HAVE_SPATIALITE
    9507             :         // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
    9508         945 :         if (!bNeedsTrustedSchema && HasExtensionsTable() &&
    9509         866 :             SQLGetInteger(
    9510             :                 hDB,
    9511             :                 "SELECT 1 FROM gpkg_extensions WHERE "
    9512             :                 "extension_name ='gdal_spatialite_computed_geom_column'",
    9513           1 :                 nullptr) == 1 &&
    9514        2960 :             SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
    9515             :         {
    9516           1 :             bNeedsTrustedSchema = true;
    9517             :         }
    9518             : #endif
    9519             : 
    9520        2015 :         if (bNeedsTrustedSchema)
    9521             :         {
    9522        1071 :             CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
    9523        1071 :             SQLCommand(hDB, "PRAGMA trusted_schema = 1");
    9524             :         }
    9525             :     }
    9526             : 
    9527             :     const char *pszPreludeStatements =
    9528        2015 :         CSLFetchNameValue(papszOpenOptions, "PRELUDE_STATEMENTS");
    9529        2015 :     if (pszPreludeStatements)
    9530             :     {
    9531           2 :         if (SQLCommand(hDB, pszPreludeStatements) != OGRERR_NONE)
    9532           0 :             return false;
    9533             :     }
    9534             : 
    9535        2015 :     return true;
    9536             : }
    9537             : 
    9538             : /************************************************************************/
    9539             : /*                   GetLayerWithGetSpatialWhereByName()                */
    9540             : /************************************************************************/
    9541             : 
    9542             : std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
    9543          90 : GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
    9544             : {
    9545             :     OGRGeoPackageLayer *poRet =
    9546          90 :         cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
    9547          90 :     return std::pair(poRet, poRet);
    9548             : }
    9549             : 
    9550             : /************************************************************************/
    9551             : /*                       CommitTransaction()                            */
    9552             : /************************************************************************/
    9553             : 
    9554         203 : OGRErr GDALGeoPackageDataset::CommitTransaction()
    9555             : 
    9556             : {
    9557         203 :     if (m_nSoftTransactionLevel == 1)
    9558             :     {
    9559         202 :         FlushMetadata();
    9560         453 :         for (auto &poLayer : m_apoLayers)
    9561             :         {
    9562         251 :             poLayer->DoJobAtTransactionCommit();
    9563             :         }
    9564             :     }
    9565             : 
    9566         203 :     return OGRSQLiteBaseDataSource::CommitTransaction();
    9567             : }
    9568             : 
    9569             : /************************************************************************/
    9570             : /*                     RollbackTransaction()                            */
    9571             : /************************************************************************/
    9572             : 
    9573          35 : OGRErr GDALGeoPackageDataset::RollbackTransaction()
    9574             : 
    9575             : {
    9576             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9577          70 :     std::vector<bool> abAddTriggers;
    9578          35 :     std::vector<bool> abTriggersDeletedInTransaction;
    9579             : #endif
    9580          35 :     if (m_nSoftTransactionLevel == 1)
    9581             :     {
    9582          34 :         FlushMetadata();
    9583          70 :         for (auto &poLayer : m_apoLayers)
    9584             :         {
    9585             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9586          36 :             abAddTriggers.push_back(poLayer->GetAddOGRFeatureCountTriggers());
    9587          36 :             abTriggersDeletedInTransaction.push_back(
    9588          36 :                 poLayer->GetOGRFeatureCountTriggersDeletedInTransaction());
    9589          36 :             poLayer->SetAddOGRFeatureCountTriggers(false);
    9590             : #endif
    9591          36 :             poLayer->DoJobAtTransactionRollback();
    9592             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9593          36 :             poLayer->DisableFeatureCount();
    9594             : #endif
    9595             :         }
    9596             :     }
    9597             : 
    9598          35 :     const OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
    9599             : 
    9600             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9601          35 :     if (!abAddTriggers.empty())
    9602             :     {
    9603          68 :         for (size_t i = 0; i < m_apoLayers.size(); ++i)
    9604             :         {
    9605          36 :             auto &poLayer = m_apoLayers[i];
    9606          36 :             if (abTriggersDeletedInTransaction[i])
    9607             :             {
    9608           7 :                 poLayer->SetOGRFeatureCountTriggersEnabled(true);
    9609             :             }
    9610             :             else
    9611             :             {
    9612          29 :                 poLayer->SetAddOGRFeatureCountTriggers(abAddTriggers[i]);
    9613             :             }
    9614             :         }
    9615             :     }
    9616             : #endif
    9617          70 :     return eErr;
    9618             : }
    9619             : 
    9620             : /************************************************************************/
    9621             : /*                       GetGeometryTypeString()                        */
    9622             : /************************************************************************/
    9623             : 
    9624             : const char *
    9625        1407 : GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
    9626             : {
    9627        1407 :     const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
    9628        1419 :     if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
    9629          12 :         CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
    9630             :     {
    9631           0 :         pszGPKGGeomType = "GEOMCOLLECTION";
    9632             :     }
    9633        1407 :     return pszGPKGGeomType;
    9634             : }
    9635             : 
    9636             : /************************************************************************/
    9637             : /*                           GetFieldDomainNames()                      */
    9638             : /************************************************************************/
    9639             : 
    9640             : std::vector<std::string>
    9641          10 : GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
    9642             : {
    9643          10 :     if (!HasDataColumnConstraintsTable())
    9644           3 :         return std::vector<std::string>();
    9645             : 
    9646          14 :     std::vector<std::string> oDomainNamesList;
    9647             : 
    9648           7 :     std::unique_ptr<SQLResult> oResultTable;
    9649             :     {
    9650             :         std::string osSQL =
    9651             :             "SELECT DISTINCT constraint_name "
    9652             :             "FROM gpkg_data_column_constraints "
    9653             :             "WHERE constraint_name NOT LIKE '_%_domain_description' "
    9654             :             "ORDER BY constraint_name "
    9655           7 :             "LIMIT 10000"  // to avoid denial of service
    9656             :             ;
    9657           7 :         oResultTable = SQLQuery(hDB, osSQL.c_str());
    9658           7 :         if (!oResultTable)
    9659           0 :             return oDomainNamesList;
    9660             :     }
    9661             : 
    9662           7 :     if (oResultTable->RowCount() == 10000)
    9663             :     {
    9664           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9665             :                  "Number of rows returned for field domain names has been "
    9666             :                  "truncated.");
    9667             :     }
    9668           7 :     else if (oResultTable->RowCount() > 0)
    9669             :     {
    9670           7 :         oDomainNamesList.reserve(oResultTable->RowCount());
    9671          89 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    9672             :         {
    9673          82 :             const char *pszConstraintName = oResultTable->GetValue(0, i);
    9674          82 :             if (!pszConstraintName)
    9675           0 :                 continue;
    9676             : 
    9677          82 :             oDomainNamesList.emplace_back(pszConstraintName);
    9678             :         }
    9679             :     }
    9680             : 
    9681           7 :     return oDomainNamesList;
    9682             : }
    9683             : 
    9684             : /************************************************************************/
    9685             : /*                           GetFieldDomain()                           */
    9686             : /************************************************************************/
    9687             : 
    9688             : const OGRFieldDomain *
    9689         102 : GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
    9690             : {
    9691         102 :     const auto baseRet = GDALDataset::GetFieldDomain(name);
    9692         102 :     if (baseRet)
    9693          42 :         return baseRet;
    9694             : 
    9695          60 :     if (!HasDataColumnConstraintsTable())
    9696           4 :         return nullptr;
    9697             : 
    9698          56 :     const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
    9699          56 :     const char *min_is_inclusive =
    9700          56 :         bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
    9701          56 :     const char *max_is_inclusive =
    9702          56 :         bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
    9703             : 
    9704          56 :     std::unique_ptr<SQLResult> oResultTable;
    9705             :     // Note: for coded domains, we use a little trick by using a dummy
    9706             :     // _{domainname}_domain_description enum that has a single entry whose
    9707             :     // description is the description of the main domain.
    9708             :     {
    9709          56 :         char *pszSQL = sqlite3_mprintf(
    9710             :             "SELECT constraint_type, value, min, %s, "
    9711             :             "max, %s, description, constraint_name "
    9712             :             "FROM gpkg_data_column_constraints "
    9713             :             "WHERE constraint_name IN ('%q', "
    9714             :             "'_%q_domain_description') "
    9715             :             "AND length(constraint_type) < 100 "  // to
    9716             :                                                   // avoid
    9717             :                                                   // denial
    9718             :                                                   // of
    9719             :                                                   // service
    9720             :             "AND (value IS NULL OR length(value) < "
    9721             :             "10000) "  // to avoid denial
    9722             :                        // of service
    9723             :             "AND (description IS NULL OR "
    9724             :             "length(description) < 10000) "  // to
    9725             :                                              // avoid
    9726             :                                              // denial
    9727             :                                              // of
    9728             :                                              // service
    9729             :             "ORDER BY value "
    9730             :             "LIMIT 10000",  // to avoid denial of
    9731             :                             // service
    9732             :             min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
    9733          56 :         oResultTable = SQLQuery(hDB, pszSQL);
    9734          56 :         sqlite3_free(pszSQL);
    9735          56 :         if (!oResultTable)
    9736           0 :             return nullptr;
    9737             :     }
    9738          56 :     if (oResultTable->RowCount() == 0)
    9739             :     {
    9740          15 :         return nullptr;
    9741             :     }
    9742          41 :     if (oResultTable->RowCount() == 10000)
    9743             :     {
    9744           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9745             :                  "Number of rows returned for field domain %s has been "
    9746             :                  "truncated.",
    9747             :                  name.c_str());
    9748             :     }
    9749             : 
    9750             :     // Try to find the field domain data type from fields that implement it
    9751          41 :     int nFieldType = -1;
    9752          41 :     OGRFieldSubType eSubType = OFSTNone;
    9753          41 :     if (HasDataColumnsTable())
    9754             :     {
    9755          36 :         char *pszSQL = sqlite3_mprintf(
    9756             :             "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
    9757             :             "constraint_name = '%q' LIMIT 10",
    9758             :             name.c_str());
    9759          72 :         auto oResultTable2 = SQLQuery(hDB, pszSQL);
    9760          36 :         sqlite3_free(pszSQL);
    9761          36 :         if (oResultTable2 && oResultTable2->RowCount() >= 1)
    9762             :         {
    9763          46 :             for (int iRecord = 0; iRecord < oResultTable2->RowCount();
    9764             :                  iRecord++)
    9765             :             {
    9766          23 :                 const char *pszTableName = oResultTable2->GetValue(0, iRecord);
    9767          23 :                 const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
    9768          23 :                 if (pszTableName == nullptr || pszColumnName == nullptr)
    9769           0 :                     continue;
    9770             :                 OGRLayer *poLayer =
    9771          46 :                     const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
    9772          23 :                         pszTableName);
    9773          23 :                 if (poLayer)
    9774             :                 {
    9775          23 :                     const auto poFDefn = poLayer->GetLayerDefn();
    9776          23 :                     int nIdx = poFDefn->GetFieldIndex(pszColumnName);
    9777          23 :                     if (nIdx >= 0)
    9778             :                     {
    9779          23 :                         const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
    9780          23 :                         const auto eType = poFieldDefn->GetType();
    9781          23 :                         if (nFieldType < 0)
    9782             :                         {
    9783          23 :                             nFieldType = eType;
    9784          23 :                             eSubType = poFieldDefn->GetSubType();
    9785             :                         }
    9786           0 :                         else if ((eType == OFTInteger64 || eType == OFTReal) &&
    9787             :                                  nFieldType == OFTInteger)
    9788             :                         {
    9789             :                             // ok
    9790             :                         }
    9791           0 :                         else if (eType == OFTInteger &&
    9792           0 :                                  (nFieldType == OFTInteger64 ||
    9793             :                                   nFieldType == OFTReal))
    9794             :                         {
    9795           0 :                             nFieldType = OFTInteger;
    9796           0 :                             eSubType = OFSTNone;
    9797             :                         }
    9798           0 :                         else if (nFieldType != eType)
    9799             :                         {
    9800           0 :                             nFieldType = -1;
    9801           0 :                             eSubType = OFSTNone;
    9802           0 :                             break;
    9803             :                         }
    9804             :                     }
    9805             :                 }
    9806             :             }
    9807             :         }
    9808             :     }
    9809             : 
    9810          41 :     std::unique_ptr<OGRFieldDomain> poDomain;
    9811          82 :     std::vector<OGRCodedValue> asValues;
    9812          41 :     bool error = false;
    9813          82 :     CPLString osLastConstraintType;
    9814          41 :     int nFieldTypeFromEnumCode = -1;
    9815          82 :     std::string osConstraintDescription;
    9816          82 :     std::string osDescrConstraintName("_");
    9817          41 :     osDescrConstraintName += name;
    9818          41 :     osDescrConstraintName += "_domain_description";
    9819         100 :     for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
    9820             :     {
    9821          63 :         const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
    9822          63 :         if (pszConstraintType == nullptr)
    9823           1 :             continue;
    9824          63 :         const char *pszValue = oResultTable->GetValue(1, iRecord);
    9825          63 :         const char *pszMin = oResultTable->GetValue(2, iRecord);
    9826             :         const bool bIsMinIncluded =
    9827          63 :             oResultTable->GetValueAsInteger(3, iRecord) == 1;
    9828          63 :         const char *pszMax = oResultTable->GetValue(4, iRecord);
    9829             :         const bool bIsMaxIncluded =
    9830          63 :             oResultTable->GetValueAsInteger(5, iRecord) == 1;
    9831          63 :         const char *pszDescription = oResultTable->GetValue(6, iRecord);
    9832          63 :         const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
    9833             : 
    9834          63 :         if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
    9835             :         {
    9836           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    9837             :                      "Only constraint of type 'enum' can have multiple rows");
    9838           1 :             error = true;
    9839           4 :             break;
    9840             :         }
    9841             : 
    9842          62 :         if (strcmp(pszConstraintType, "enum") == 0)
    9843             :         {
    9844          42 :             if (pszValue == nullptr)
    9845             :             {
    9846           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    9847             :                          "NULL in 'value' column of enumeration");
    9848           1 :                 error = true;
    9849           1 :                 break;
    9850             :             }
    9851          41 :             if (osDescrConstraintName == pszConstraintName)
    9852             :             {
    9853           1 :                 if (pszDescription)
    9854             :                 {
    9855           1 :                     osConstraintDescription = pszDescription;
    9856             :                 }
    9857           1 :                 continue;
    9858             :             }
    9859          40 :             if (asValues.empty())
    9860             :             {
    9861          20 :                 asValues.reserve(oResultTable->RowCount() + 1);
    9862             :             }
    9863             :             OGRCodedValue cv;
    9864             :             // intended: the 'value' column in GPKG is actually the code
    9865          40 :             cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
    9866          40 :             if (cv.pszCode == nullptr)
    9867             :             {
    9868           0 :                 error = true;
    9869           0 :                 break;
    9870             :             }
    9871          40 :             if (pszDescription)
    9872             :             {
    9873          29 :                 cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
    9874          29 :                 if (cv.pszValue == nullptr)
    9875             :                 {
    9876           0 :                     VSIFree(cv.pszCode);
    9877           0 :                     error = true;
    9878           0 :                     break;
    9879             :                 }
    9880             :             }
    9881             :             else
    9882             :             {
    9883          11 :                 cv.pszValue = nullptr;
    9884             :             }
    9885             : 
    9886             :             // If we can't get the data type from field definition, guess it
    9887             :             // from code.
    9888          40 :             if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
    9889             :             {
    9890          18 :                 switch (CPLGetValueType(cv.pszCode))
    9891             :                 {
    9892          13 :                     case CPL_VALUE_INTEGER:
    9893             :                     {
    9894          13 :                         if (nFieldTypeFromEnumCode != OFTReal &&
    9895             :                             nFieldTypeFromEnumCode != OFTInteger64)
    9896             :                         {
    9897           9 :                             const auto nVal = CPLAtoGIntBig(cv.pszCode);
    9898          17 :                             if (nVal < std::numeric_limits<int>::min() ||
    9899           8 :                                 nVal > std::numeric_limits<int>::max())
    9900             :                             {
    9901           3 :                                 nFieldTypeFromEnumCode = OFTInteger64;
    9902             :                             }
    9903             :                             else
    9904             :                             {
    9905           6 :                                 nFieldTypeFromEnumCode = OFTInteger;
    9906             :                             }
    9907             :                         }
    9908          13 :                         break;
    9909             :                     }
    9910             : 
    9911           3 :                     case CPL_VALUE_REAL:
    9912           3 :                         nFieldTypeFromEnumCode = OFTReal;
    9913           3 :                         break;
    9914             : 
    9915           2 :                     case CPL_VALUE_STRING:
    9916           2 :                         nFieldTypeFromEnumCode = OFTString;
    9917           2 :                         break;
    9918             :                 }
    9919             :             }
    9920             : 
    9921          40 :             asValues.emplace_back(cv);
    9922             :         }
    9923          20 :         else if (strcmp(pszConstraintType, "range") == 0)
    9924             :         {
    9925             :             OGRField sMin;
    9926             :             OGRField sMax;
    9927          14 :             OGR_RawField_SetUnset(&sMin);
    9928          14 :             OGR_RawField_SetUnset(&sMax);
    9929          14 :             if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
    9930           8 :                 nFieldType = OFTReal;
    9931          27 :             if (pszMin != nullptr &&
    9932          13 :                 CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
    9933             :             {
    9934          10 :                 if (nFieldType == OFTInteger)
    9935           3 :                     sMin.Integer = atoi(pszMin);
    9936           7 :                 else if (nFieldType == OFTInteger64)
    9937           3 :                     sMin.Integer64 = CPLAtoGIntBig(pszMin);
    9938             :                 else /* if( nFieldType == OFTReal ) */
    9939           4 :                     sMin.Real = CPLAtof(pszMin);
    9940             :             }
    9941          27 :             if (pszMax != nullptr &&
    9942          13 :                 CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
    9943             :             {
    9944          10 :                 if (nFieldType == OFTInteger)
    9945           3 :                     sMax.Integer = atoi(pszMax);
    9946           7 :                 else if (nFieldType == OFTInteger64)
    9947           3 :                     sMax.Integer64 = CPLAtoGIntBig(pszMax);
    9948             :                 else /* if( nFieldType == OFTReal ) */
    9949           4 :                     sMax.Real = CPLAtof(pszMax);
    9950             :             }
    9951          14 :             poDomain = std::make_unique<OGRRangeFieldDomain>(
    9952          14 :                 name, pszDescription ? pszDescription : "",
    9953          28 :                 static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
    9954          14 :                 bIsMinIncluded, sMax, bIsMaxIncluded);
    9955             :         }
    9956           6 :         else if (strcmp(pszConstraintType, "glob") == 0)
    9957             :         {
    9958           5 :             if (pszValue == nullptr)
    9959             :             {
    9960           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    9961             :                          "NULL in 'value' column of glob");
    9962           1 :                 error = true;
    9963           1 :                 break;
    9964             :             }
    9965           4 :             if (nFieldType < 0)
    9966           1 :                 nFieldType = OFTString;
    9967           4 :             poDomain = std::make_unique<OGRGlobFieldDomain>(
    9968           4 :                 name, pszDescription ? pszDescription : "",
    9969          12 :                 static_cast<OGRFieldType>(nFieldType), eSubType, pszValue);
    9970             :         }
    9971             :         else
    9972             :         {
    9973           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    9974             :                      "Unhandled constraint_type: %s", pszConstraintType);
    9975           1 :             error = true;
    9976           1 :             break;
    9977             :         }
    9978             : 
    9979          58 :         osLastConstraintType = pszConstraintType;
    9980             :     }
    9981             : 
    9982          41 :     if (!asValues.empty())
    9983             :     {
    9984          20 :         if (nFieldType < 0)
    9985           9 :             nFieldType = nFieldTypeFromEnumCode;
    9986          20 :         poDomain = std::make_unique<OGRCodedFieldDomain>(
    9987             :             name, osConstraintDescription,
    9988          40 :             static_cast<OGRFieldType>(nFieldType), eSubType,
    9989          40 :             std::move(asValues));
    9990             :     }
    9991             : 
    9992          41 :     if (error)
    9993             :     {
    9994           4 :         return nullptr;
    9995             :     }
    9996             : 
    9997          37 :     m_oMapFieldDomains[name] = std::move(poDomain);
    9998          37 :     return GDALDataset::GetFieldDomain(name);
    9999             : }
   10000             : 
   10001             : /************************************************************************/
   10002             : /*                           AddFieldDomain()                           */
   10003             : /************************************************************************/
   10004             : 
   10005          18 : bool GDALGeoPackageDataset::AddFieldDomain(
   10006             :     std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
   10007             : {
   10008          36 :     const std::string domainName(domain->GetName());
   10009          18 :     if (!GetUpdate())
   10010             :     {
   10011           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10012             :                  "AddFieldDomain() not supported on read-only dataset");
   10013           0 :         return false;
   10014             :     }
   10015          18 :     if (GetFieldDomain(domainName) != nullptr)
   10016             :     {
   10017           1 :         failureReason = "A domain of identical name already exists";
   10018           1 :         return false;
   10019             :     }
   10020          17 :     if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
   10021           0 :         return false;
   10022             : 
   10023          17 :     const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
   10024          17 :     const char *min_is_inclusive =
   10025          17 :         bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
   10026          17 :     const char *max_is_inclusive =
   10027          17 :         bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
   10028             : 
   10029          17 :     const auto &osDescription = domain->GetDescription();
   10030          17 :     switch (domain->GetDomainType())
   10031             :     {
   10032          11 :         case OFDT_CODED:
   10033             :         {
   10034             :             const auto poCodedDomain =
   10035          11 :                 cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
   10036          11 :             if (!osDescription.empty())
   10037             :             {
   10038             :                 // We use a little trick by using a dummy
   10039             :                 // _{domainname}_domain_description enum that has a single
   10040             :                 // entry whose description is the description of the main
   10041             :                 // domain.
   10042           1 :                 char *pszSQL = sqlite3_mprintf(
   10043             :                     "INSERT INTO gpkg_data_column_constraints ("
   10044             :                     "constraint_name, constraint_type, value, "
   10045             :                     "min, %s, max, %s, "
   10046             :                     "description) VALUES ("
   10047             :                     "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
   10048             :                     "NULL, %Q)",
   10049             :                     min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10050             :                     osDescription.c_str());
   10051           1 :                 CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
   10052           1 :                 sqlite3_free(pszSQL);
   10053             :             }
   10054          11 :             const auto &enumeration = poCodedDomain->GetEnumeration();
   10055          33 :             for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
   10056             :             {
   10057          22 :                 char *pszSQL = sqlite3_mprintf(
   10058             :                     "INSERT INTO gpkg_data_column_constraints ("
   10059             :                     "constraint_name, constraint_type, value, "
   10060             :                     "min, %s, max, %s, "
   10061             :                     "description) VALUES ("
   10062             :                     "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
   10063             :                     min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10064          22 :                     enumeration[i].pszCode, enumeration[i].pszValue);
   10065          22 :                 bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
   10066          22 :                 sqlite3_free(pszSQL);
   10067          22 :                 if (!ok)
   10068           0 :                     return false;
   10069             :             }
   10070          11 :             break;
   10071             :         }
   10072             : 
   10073           5 :         case OFDT_RANGE:
   10074             :         {
   10075             :             const auto poRangeDomain =
   10076           5 :                 cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
   10077           5 :             const auto eFieldType = poRangeDomain->GetFieldType();
   10078           5 :             if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
   10079             :                 eFieldType != OFTReal)
   10080             :             {
   10081             :                 failureReason = "Only range domains of numeric type are "
   10082           0 :                                 "supported in GeoPackage";
   10083           0 :                 return false;
   10084             :             }
   10085             : 
   10086           5 :             double dfMin = -std::numeric_limits<double>::infinity();
   10087           5 :             double dfMax = std::numeric_limits<double>::infinity();
   10088           5 :             bool bMinIsInclusive = true;
   10089           5 :             const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
   10090           5 :             bool bMaxIsInclusive = true;
   10091           5 :             const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
   10092           5 :             if (eFieldType == OFTInteger)
   10093             :             {
   10094           1 :                 if (!OGR_RawField_IsUnset(&sMin))
   10095           1 :                     dfMin = sMin.Integer;
   10096           1 :                 if (!OGR_RawField_IsUnset(&sMax))
   10097           1 :                     dfMax = sMax.Integer;
   10098             :             }
   10099           4 :             else if (eFieldType == OFTInteger64)
   10100             :             {
   10101           1 :                 if (!OGR_RawField_IsUnset(&sMin))
   10102           1 :                     dfMin = static_cast<double>(sMin.Integer64);
   10103           1 :                 if (!OGR_RawField_IsUnset(&sMax))
   10104           1 :                     dfMax = static_cast<double>(sMax.Integer64);
   10105             :             }
   10106             :             else /* if( eFieldType == OFTReal ) */
   10107             :             {
   10108           3 :                 if (!OGR_RawField_IsUnset(&sMin))
   10109           3 :                     dfMin = sMin.Real;
   10110           3 :                 if (!OGR_RawField_IsUnset(&sMax))
   10111           3 :                     dfMax = sMax.Real;
   10112             :             }
   10113             : 
   10114           5 :             sqlite3_stmt *hInsertStmt = nullptr;
   10115             :             const char *pszSQL =
   10116           5 :                 CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
   10117             :                            "constraint_name, constraint_type, value, "
   10118             :                            "min, %s, max, %s, "
   10119             :                            "description) VALUES ("
   10120             :                            "?, 'range', NULL, ?, ?, ?, ?, ?)",
   10121             :                            min_is_inclusive, max_is_inclusive);
   10122           5 :             if (SQLPrepareWithError(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
   10123             :                 SQLITE_OK)
   10124             :             {
   10125           0 :                 return false;
   10126             :             }
   10127           5 :             sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
   10128           5 :                               static_cast<int>(domainName.size()),
   10129             :                               SQLITE_TRANSIENT);
   10130           5 :             sqlite3_bind_double(hInsertStmt, 2, dfMin);
   10131           5 :             sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
   10132           5 :             sqlite3_bind_double(hInsertStmt, 4, dfMax);
   10133           5 :             sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
   10134           5 :             if (osDescription.empty())
   10135             :             {
   10136           3 :                 sqlite3_bind_null(hInsertStmt, 6);
   10137             :             }
   10138             :             else
   10139             :             {
   10140           2 :                 sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
   10141           2 :                                   static_cast<int>(osDescription.size()),
   10142             :                                   SQLITE_TRANSIENT);
   10143             :             }
   10144           5 :             const int sqlite_err = sqlite3_step(hInsertStmt);
   10145           5 :             sqlite3_finalize(hInsertStmt);
   10146           5 :             if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
   10147             :             {
   10148           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10149             :                          "failed to execute insertion '%s': %s", pszSQL,
   10150             :                          sqlite3_errmsg(hDB));
   10151           0 :                 return false;
   10152             :             }
   10153             : 
   10154           5 :             break;
   10155             :         }
   10156             : 
   10157           1 :         case OFDT_GLOB:
   10158             :         {
   10159             :             const auto poGlobDomain =
   10160           1 :                 cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
   10161           2 :             char *pszSQL = sqlite3_mprintf(
   10162             :                 "INSERT INTO gpkg_data_column_constraints ("
   10163             :                 "constraint_name, constraint_type, value, "
   10164             :                 "min, %s, max, %s, "
   10165             :                 "description) VALUES ("
   10166             :                 "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
   10167             :                 min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10168           1 :                 poGlobDomain->GetGlob().c_str(),
   10169           2 :                 osDescription.empty() ? nullptr : osDescription.c_str());
   10170           1 :             bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
   10171           1 :             sqlite3_free(pszSQL);
   10172           1 :             if (!ok)
   10173           0 :                 return false;
   10174             : 
   10175           1 :             break;
   10176             :         }
   10177             :     }
   10178             : 
   10179          17 :     m_oMapFieldDomains[domainName] = std::move(domain);
   10180          17 :     return true;
   10181             : }
   10182             : 
   10183             : /************************************************************************/
   10184             : /*                          AddRelationship()                           */
   10185             : /************************************************************************/
   10186             : 
   10187          24 : bool GDALGeoPackageDataset::AddRelationship(
   10188             :     std::unique_ptr<GDALRelationship> &&relationship,
   10189             :     std::string &failureReason)
   10190             : {
   10191          24 :     if (!GetUpdate())
   10192             :     {
   10193           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10194             :                  "AddRelationship() not supported on read-only dataset");
   10195           0 :         return false;
   10196             :     }
   10197             : 
   10198             :     const std::string osRelationshipName = GenerateNameForRelationship(
   10199          24 :         relationship->GetLeftTableName().c_str(),
   10200          24 :         relationship->GetRightTableName().c_str(),
   10201          96 :         relationship->GetRelatedTableType().c_str());
   10202             :     // sanity checks
   10203          24 :     if (GetRelationship(osRelationshipName) != nullptr)
   10204             :     {
   10205           1 :         failureReason = "A relationship of identical name already exists";
   10206           1 :         return false;
   10207             :     }
   10208             : 
   10209          23 :     if (!ValidateRelationship(relationship.get(), failureReason))
   10210             :     {
   10211          14 :         return false;
   10212             :     }
   10213             : 
   10214           9 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
   10215             :     {
   10216           0 :         return false;
   10217             :     }
   10218           9 :     if (!CreateRelationsTableIfNecessary())
   10219             :     {
   10220           0 :         failureReason = "Could not create gpkgext_relations table";
   10221           0 :         return false;
   10222             :     }
   10223           9 :     if (SQLGetInteger(GetDB(),
   10224             :                       "SELECT 1 FROM gpkg_extensions WHERE "
   10225             :                       "table_name = 'gpkgext_relations'",
   10226           9 :                       nullptr) != 1)
   10227             :     {
   10228           4 :         if (OGRERR_NONE !=
   10229           4 :             SQLCommand(
   10230             :                 GetDB(),
   10231             :                 "INSERT INTO gpkg_extensions "
   10232             :                 "(table_name,column_name,extension_name,definition,scope) "
   10233             :                 "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
   10234             :                 "'http://www.geopackage.org/18-000.html', "
   10235             :                 "'read-write')"))
   10236             :         {
   10237             :             failureReason =
   10238           0 :                 "Could not create gpkg_extensions entry for gpkgext_relations";
   10239           0 :             return false;
   10240             :         }
   10241             :     }
   10242             : 
   10243           9 :     const std::string &osLeftTableName = relationship->GetLeftTableName();
   10244           9 :     const std::string &osRightTableName = relationship->GetRightTableName();
   10245           9 :     const auto &aosLeftTableFields = relationship->GetLeftTableFields();
   10246           9 :     const auto &aosRightTableFields = relationship->GetRightTableFields();
   10247             : 
   10248          18 :     std::string osRelatedTableType = relationship->GetRelatedTableType();
   10249           9 :     if (osRelatedTableType.empty())
   10250             :     {
   10251           5 :         osRelatedTableType = "features";
   10252             :     }
   10253             : 
   10254             :     // generate mapping table if not set
   10255          18 :     CPLString osMappingTableName = relationship->GetMappingTableName();
   10256           9 :     if (osMappingTableName.empty())
   10257             :     {
   10258           3 :         int nIndex = 1;
   10259           3 :         osMappingTableName = osLeftTableName + "_" + osRightTableName;
   10260           3 :         while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
   10261             :         {
   10262           0 :             nIndex += 1;
   10263             :             osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
   10264           0 :                                       osRightTableName.c_str(), nIndex);
   10265             :         }
   10266             : 
   10267             :         // determine whether base/related keys are unique
   10268           3 :         bool bBaseKeyIsUnique = false;
   10269             :         {
   10270             :             const std::set<std::string> uniqueBaseFieldsUC =
   10271             :                 SQLGetUniqueFieldUCConstraints(GetDB(),
   10272           6 :                                                osLeftTableName.c_str());
   10273           6 :             if (uniqueBaseFieldsUC.find(
   10274           3 :                     CPLString(aosLeftTableFields[0]).toupper()) !=
   10275           6 :                 uniqueBaseFieldsUC.end())
   10276             :             {
   10277           2 :                 bBaseKeyIsUnique = true;
   10278             :             }
   10279             :         }
   10280           3 :         bool bRelatedKeyIsUnique = false;
   10281             :         {
   10282             :             const std::set<std::string> uniqueRelatedFieldsUC =
   10283             :                 SQLGetUniqueFieldUCConstraints(GetDB(),
   10284           6 :                                                osRightTableName.c_str());
   10285           6 :             if (uniqueRelatedFieldsUC.find(
   10286           3 :                     CPLString(aosRightTableFields[0]).toupper()) !=
   10287           6 :                 uniqueRelatedFieldsUC.end())
   10288             :             {
   10289           2 :                 bRelatedKeyIsUnique = true;
   10290             :             }
   10291             :         }
   10292             : 
   10293             :         // create mapping table
   10294             : 
   10295           3 :         std::string osBaseIdDefinition = "base_id INTEGER";
   10296           3 :         if (bBaseKeyIsUnique)
   10297             :         {
   10298           2 :             char *pszSQL = sqlite3_mprintf(
   10299             :                 " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
   10300             :                 "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
   10301             :                 "DEFERRED",
   10302             :                 osMappingTableName.c_str(), osLeftTableName.c_str(),
   10303           2 :                 aosLeftTableFields[0].c_str());
   10304           2 :             osBaseIdDefinition += pszSQL;
   10305           2 :             sqlite3_free(pszSQL);
   10306             :         }
   10307             : 
   10308           3 :         std::string osRelatedIdDefinition = "related_id INTEGER";
   10309           3 :         if (bRelatedKeyIsUnique)
   10310             :         {
   10311           2 :             char *pszSQL = sqlite3_mprintf(
   10312             :                 " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
   10313             :                 "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
   10314             :                 "DEFERRED",
   10315             :                 osMappingTableName.c_str(), osRightTableName.c_str(),
   10316           2 :                 aosRightTableFields[0].c_str());
   10317           2 :             osRelatedIdDefinition += pszSQL;
   10318           2 :             sqlite3_free(pszSQL);
   10319             :         }
   10320             : 
   10321           3 :         char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
   10322             :                                        "id INTEGER PRIMARY KEY AUTOINCREMENT, "
   10323             :                                        "%s, %s);",
   10324             :                                        osMappingTableName.c_str(),
   10325             :                                        osBaseIdDefinition.c_str(),
   10326             :                                        osRelatedIdDefinition.c_str());
   10327           3 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
   10328           3 :         sqlite3_free(pszSQL);
   10329           3 :         if (eErr != OGRERR_NONE)
   10330             :         {
   10331             :             failureReason =
   10332           0 :                 ("Could not create mapping table " + osMappingTableName)
   10333           0 :                     .c_str();
   10334           0 :             return false;
   10335             :         }
   10336             : 
   10337             :         /*
   10338             :          * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
   10339             :          * The related tables extension explicitly states that the mapping table should only be
   10340             :          * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
   10341             :          * https://github.com/opengeospatial/geopackage/issues/679).
   10342             :          *
   10343             :          * However, if we don't insert the mapping table into gpkg_contents then it is no longer
   10344             :          * visible to some clients (eg ESRI software only allows opening tables that are present
   10345             :          * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
   10346             :          *
   10347             :          * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
   10348             :          */
   10349           3 :         pszSQL = sqlite3_mprintf(
   10350             :             "INSERT INTO gpkg_contents "
   10351             :             "(table_name,data_type,identifier,description,last_change,srs_id) "
   10352             :             "VALUES "
   10353             :             "('%q','attributes','%q','Mapping table for relationship between "
   10354             :             "%q and %q',%s,0)",
   10355             :             osMappingTableName.c_str(), /*table_name*/
   10356             :             osMappingTableName.c_str(), /*identifier*/
   10357             :             osLeftTableName.c_str(),    /*description left table name*/
   10358             :             osRightTableName.c_str(),   /*description right table name*/
   10359           6 :             GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
   10360             : 
   10361             :         // Note -- we explicitly ignore failures here, because hey, we aren't really
   10362             :         // supposed to be adding this table to gpkg_contents anyway!
   10363           3 :         (void)SQLCommand(hDB, pszSQL);
   10364           3 :         sqlite3_free(pszSQL);
   10365             : 
   10366           3 :         pszSQL = sqlite3_mprintf(
   10367             :             "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
   10368             :             osMappingTableName.c_str(), osMappingTableName.c_str());
   10369           3 :         eErr = SQLCommand(hDB, pszSQL);
   10370           3 :         sqlite3_free(pszSQL);
   10371           3 :         if (eErr != OGRERR_NONE)
   10372             :         {
   10373           0 :             failureReason = ("Could not create index for " +
   10374           0 :                              osMappingTableName + " (base_id)")
   10375           0 :                                 .c_str();
   10376           0 :             return false;
   10377             :         }
   10378             : 
   10379           3 :         pszSQL = sqlite3_mprintf(
   10380             :             "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
   10381             :             osMappingTableName.c_str(), osMappingTableName.c_str());
   10382           3 :         eErr = SQLCommand(hDB, pszSQL);
   10383           3 :         sqlite3_free(pszSQL);
   10384           3 :         if (eErr != OGRERR_NONE)
   10385             :         {
   10386           0 :             failureReason = ("Could not create index for " +
   10387           0 :                              osMappingTableName + " (related_id)")
   10388           0 :                                 .c_str();
   10389           0 :             return false;
   10390             :         }
   10391             :     }
   10392             :     else
   10393             :     {
   10394             :         // validate mapping table structure
   10395           6 :         if (OGRGeoPackageTableLayer *poLayer =
   10396           6 :                 cpl::down_cast<OGRGeoPackageTableLayer *>(
   10397           6 :                     GetLayerByName(osMappingTableName)))
   10398             :         {
   10399           4 :             if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
   10400             :             {
   10401             :                 failureReason =
   10402           2 :                     ("Field base_id must exist in " + osMappingTableName)
   10403           1 :                         .c_str();
   10404           1 :                 return false;
   10405             :             }
   10406           3 :             if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
   10407             :             {
   10408             :                 failureReason =
   10409           2 :                     ("Field related_id must exist in " + osMappingTableName)
   10410           1 :                         .c_str();
   10411           1 :                 return false;
   10412             :             }
   10413             :         }
   10414             :         else
   10415             :         {
   10416             :             failureReason =
   10417           2 :                 ("Could not retrieve table " + osMappingTableName).c_str();
   10418           2 :             return false;
   10419             :         }
   10420             :     }
   10421             : 
   10422           5 :     char *pszSQL = sqlite3_mprintf(
   10423             :         "INSERT INTO gpkg_extensions "
   10424             :         "(table_name,column_name,extension_name,definition,scope) "
   10425             :         "VALUES ('%q', NULL, 'gpkg_related_tables', "
   10426             :         "'http://www.geopackage.org/18-000.html', "
   10427             :         "'read-write')",
   10428             :         osMappingTableName.c_str());
   10429           5 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
   10430           5 :     sqlite3_free(pszSQL);
   10431           5 :     if (eErr != OGRERR_NONE)
   10432             :     {
   10433           0 :         failureReason = ("Could not insert mapping table " +
   10434           0 :                          osMappingTableName + " into gpkg_extensions")
   10435           0 :                             .c_str();
   10436           0 :         return false;
   10437             :     }
   10438             : 
   10439          15 :     pszSQL = sqlite3_mprintf(
   10440             :         "INSERT INTO gpkgext_relations "
   10441             :         "(base_table_name,base_primary_column,related_table_name,related_"
   10442             :         "primary_column,relation_name,mapping_table_name) "
   10443             :         "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
   10444           5 :         osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
   10445           5 :         osRightTableName.c_str(), aosRightTableFields[0].c_str(),
   10446             :         osRelatedTableType.c_str(), osMappingTableName.c_str());
   10447           5 :     eErr = SQLCommand(hDB, pszSQL);
   10448           5 :     sqlite3_free(pszSQL);
   10449           5 :     if (eErr != OGRERR_NONE)
   10450             :     {
   10451           0 :         failureReason = "Could not insert relationship into gpkgext_relations";
   10452           0 :         return false;
   10453             :     }
   10454             : 
   10455           5 :     ClearCachedRelationships();
   10456           5 :     LoadRelationships();
   10457           5 :     return true;
   10458             : }
   10459             : 
   10460             : /************************************************************************/
   10461             : /*                         DeleteRelationship()                         */
   10462             : /************************************************************************/
   10463             : 
   10464           4 : bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
   10465             :                                                std::string &failureReason)
   10466             : {
   10467           4 :     if (eAccess != GA_Update)
   10468             :     {
   10469           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10470             :                  "DeleteRelationship() not supported on read-only dataset");
   10471           0 :         return false;
   10472             :     }
   10473             : 
   10474             :     // ensure relationships are up to date before we try to remove one
   10475           4 :     ClearCachedRelationships();
   10476           4 :     LoadRelationships();
   10477             : 
   10478           8 :     std::string osMappingTableName;
   10479             :     {
   10480           4 :         const GDALRelationship *poRelationship = GetRelationship(name);
   10481           4 :         if (poRelationship == nullptr)
   10482             :         {
   10483           1 :             failureReason = "Could not find relationship with name " + name;
   10484           1 :             return false;
   10485             :         }
   10486             : 
   10487           3 :         osMappingTableName = poRelationship->GetMappingTableName();
   10488             :     }
   10489             : 
   10490             :     // DeleteLayerCommon will delete existing relationship objects, so we can't
   10491             :     // refer to poRelationship or any of its members previously obtained here
   10492           3 :     if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
   10493             :     {
   10494             :         failureReason =
   10495           0 :             "Could not remove mapping layer name " + osMappingTableName;
   10496             : 
   10497             :         // relationships may have been left in an inconsistent state -- reload
   10498             :         // them now
   10499           0 :         ClearCachedRelationships();
   10500           0 :         LoadRelationships();
   10501           0 :         return false;
   10502             :     }
   10503             : 
   10504           3 :     ClearCachedRelationships();
   10505           3 :     LoadRelationships();
   10506           3 :     return true;
   10507             : }
   10508             : 
   10509             : /************************************************************************/
   10510             : /*                        UpdateRelationship()                          */
   10511             : /************************************************************************/
   10512             : 
   10513           6 : bool GDALGeoPackageDataset::UpdateRelationship(
   10514             :     std::unique_ptr<GDALRelationship> &&relationship,
   10515             :     std::string &failureReason)
   10516             : {
   10517           6 :     if (eAccess != GA_Update)
   10518             :     {
   10519           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10520             :                  "UpdateRelationship() not supported on read-only dataset");
   10521           0 :         return false;
   10522             :     }
   10523             : 
   10524             :     // ensure relationships are up to date before we try to update one
   10525           6 :     ClearCachedRelationships();
   10526           6 :     LoadRelationships();
   10527             : 
   10528           6 :     const std::string &osRelationshipName = relationship->GetName();
   10529           6 :     const std::string &osLeftTableName = relationship->GetLeftTableName();
   10530           6 :     const std::string &osRightTableName = relationship->GetRightTableName();
   10531           6 :     const std::string &osMappingTableName = relationship->GetMappingTableName();
   10532           6 :     const auto &aosLeftTableFields = relationship->GetLeftTableFields();
   10533           6 :     const auto &aosRightTableFields = relationship->GetRightTableFields();
   10534             : 
   10535             :     // sanity checks
   10536             :     {
   10537             :         const GDALRelationship *poExistingRelationship =
   10538           6 :             GetRelationship(osRelationshipName);
   10539           6 :         if (poExistingRelationship == nullptr)
   10540             :         {
   10541             :             failureReason =
   10542           1 :                 "The relationship should already exist to be updated";
   10543           1 :             return false;
   10544             :         }
   10545             : 
   10546           5 :         if (!ValidateRelationship(relationship.get(), failureReason))
   10547             :         {
   10548           2 :             return false;
   10549             :         }
   10550             : 
   10551             :         // we don't permit changes to the participating tables
   10552           3 :         if (osLeftTableName != poExistingRelationship->GetLeftTableName())
   10553             :         {
   10554           0 :             failureReason = ("Cannot change base table from " +
   10555           0 :                              poExistingRelationship->GetLeftTableName() +
   10556           0 :                              " to " + osLeftTableName)
   10557           0 :                                 .c_str();
   10558           0 :             return false;
   10559             :         }
   10560           3 :         if (osRightTableName != poExistingRelationship->GetRightTableName())
   10561             :         {
   10562           0 :             failureReason = ("Cannot change related table from " +
   10563           0 :                              poExistingRelationship->GetRightTableName() +
   10564           0 :                              " to " + osRightTableName)
   10565           0 :                                 .c_str();
   10566           0 :             return false;
   10567             :         }
   10568           3 :         if (osMappingTableName != poExistingRelationship->GetMappingTableName())
   10569             :         {
   10570           0 :             failureReason = ("Cannot change mapping table from " +
   10571           0 :                              poExistingRelationship->GetMappingTableName() +
   10572           0 :                              " to " + osMappingTableName)
   10573           0 :                                 .c_str();
   10574           0 :             return false;
   10575             :         }
   10576             :     }
   10577             : 
   10578           6 :     std::string osRelatedTableType = relationship->GetRelatedTableType();
   10579           3 :     if (osRelatedTableType.empty())
   10580             :     {
   10581           0 :         osRelatedTableType = "features";
   10582             :     }
   10583             : 
   10584           3 :     char *pszSQL = sqlite3_mprintf(
   10585             :         "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
   10586             :         osMappingTableName.c_str());
   10587           3 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
   10588           3 :     sqlite3_free(pszSQL);
   10589           3 :     if (eErr != OGRERR_NONE)
   10590             :     {
   10591             :         failureReason =
   10592           0 :             "Could not delete old relationship from gpkgext_relations";
   10593           0 :         return false;
   10594             :     }
   10595             : 
   10596           9 :     pszSQL = sqlite3_mprintf(
   10597             :         "INSERT INTO gpkgext_relations "
   10598             :         "(base_table_name,base_primary_column,related_table_name,related_"
   10599             :         "primary_column,relation_name,mapping_table_name) "
   10600             :         "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
   10601           3 :         osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
   10602           3 :         osRightTableName.c_str(), aosRightTableFields[0].c_str(),
   10603             :         osRelatedTableType.c_str(), osMappingTableName.c_str());
   10604           3 :     eErr = SQLCommand(hDB, pszSQL);
   10605           3 :     sqlite3_free(pszSQL);
   10606           3 :     if (eErr != OGRERR_NONE)
   10607             :     {
   10608             :         failureReason =
   10609           0 :             "Could not insert updated relationship into gpkgext_relations";
   10610           0 :         return false;
   10611             :     }
   10612             : 
   10613           3 :     ClearCachedRelationships();
   10614           3 :     LoadRelationships();
   10615           3 :     return true;
   10616             : }
   10617             : 
   10618             : /************************************************************************/
   10619             : /*                    GetSqliteMasterContent()                          */
   10620             : /************************************************************************/
   10621             : 
   10622             : const std::vector<SQLSqliteMasterContent> &
   10623           2 : GDALGeoPackageDataset::GetSqliteMasterContent()
   10624             : {
   10625           2 :     if (m_aoSqliteMasterContent.empty())
   10626             :     {
   10627             :         auto oResultTable =
   10628           2 :             SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
   10629           1 :         if (oResultTable)
   10630             :         {
   10631          58 :             for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
   10632             :             {
   10633         114 :                 SQLSqliteMasterContent row;
   10634          57 :                 const char *pszSQL = oResultTable->GetValue(0, rowCnt);
   10635          57 :                 row.osSQL = pszSQL ? pszSQL : "";
   10636          57 :                 const char *pszType = oResultTable->GetValue(1, rowCnt);
   10637          57 :                 row.osType = pszType ? pszType : "";
   10638          57 :                 const char *pszTableName = oResultTable->GetValue(2, rowCnt);
   10639          57 :                 row.osTableName = pszTableName ? pszTableName : "";
   10640          57 :                 m_aoSqliteMasterContent.emplace_back(std::move(row));
   10641             :             }
   10642             :         }
   10643             :     }
   10644           2 :     return m_aoSqliteMasterContent;
   10645             : }

Generated by: LCOV version 1.14