LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gpkg - ogrgeopackagedatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 4125 4600 89.7 %
Date: 2026-02-10 10:01:39 Functions: 141 141 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 "gdal_alg.h"
      18             : #include "gdalwarper.h"
      19             : #include "gdal_utils.h"
      20             : #include "ogrgeopackageutility.h"
      21             : #include "ogrsqliteutility.h"
      22             : #include "ogr_wkb.h"
      23             : #include "vrt/vrtdataset.h"
      24             : 
      25             : #include "tilematrixset.hpp"
      26             : 
      27             : #include <cstdlib>
      28             : 
      29             : #include <algorithm>
      30             : #include <limits>
      31             : #include <sstream>
      32             : 
      33             : #define COMPILATION_ALLOWED
      34             : #define DEFINE_OGRSQLiteSQLFunctionsSetCaseSensitiveLike
      35             : #include "ogrsqlitesqlfunctionscommon.cpp"
      36             : 
      37             : // Keep in sync prototype of those 2 functions between gdalopeninfo.cpp,
      38             : // ogrsqlitedatasource.cpp and ogrgeopackagedatasource.cpp
      39             : void GDALOpenInfoDeclareFileNotToOpen(const char *pszFilename,
      40             :                                       const GByte *pabyHeader,
      41             :                                       int nHeaderBytes);
      42             : void GDALOpenInfoUnDeclareFileNotToOpen(const char *pszFilename);
      43             : 
      44             : /************************************************************************/
      45             : /*                            Tiling schemes                            */
      46             : /************************************************************************/
      47             : 
      48             : typedef struct
      49             : {
      50             :     const char *pszName;
      51             :     int nEPSGCode;
      52             :     double dfMinX;
      53             :     double dfMaxY;
      54             :     int nTileXCountZoomLevel0;
      55             :     int nTileYCountZoomLevel0;
      56             :     int nTileWidth;
      57             :     int nTileHeight;
      58             :     double dfPixelXSizeZoomLevel0;
      59             :     double dfPixelYSizeZoomLevel0;
      60             : } TilingSchemeDefinition;
      61             : 
      62             : static const TilingSchemeDefinition asTilingSchemes[] = {
      63             :     /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
      64             :        Annex E.3 */
      65             :     {"GoogleCRS84Quad", 4326, -180.0, 180.0, 1, 1, 256, 256, 360.0 / 256,
      66             :      360.0 / 256},
      67             : 
      68             :     /* See global-mercator at
      69             :        http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
      70             :     {"PseudoTMS_GlobalMercator", 3857, -20037508.34, 20037508.34, 2, 2, 256,
      71             :      256, 78271.516, 78271.516},
      72             : };
      73             : 
      74             : // Setting it above 30 would lead to integer overflow ((1 << 31) > INT_MAX)
      75             : constexpr int MAX_ZOOM_LEVEL = 30;
      76             : 
      77             : /************************************************************************/
      78             : /*                          GetTilingScheme()                           */
      79             : /************************************************************************/
      80             : 
      81             : static std::unique_ptr<TilingSchemeDefinition>
      82         570 : GetTilingScheme(const char *pszName)
      83             : {
      84         570 :     if (EQUAL(pszName, "CUSTOM"))
      85         442 :         return nullptr;
      86             : 
      87         256 :     for (const auto &tilingScheme : asTilingSchemes)
      88             :     {
      89         195 :         if (EQUAL(pszName, tilingScheme.pszName))
      90             :         {
      91          67 :             return std::make_unique<TilingSchemeDefinition>(tilingScheme);
      92             :         }
      93             :     }
      94             : 
      95          61 :     if (EQUAL(pszName, "PseudoTMS_GlobalGeodetic"))
      96           6 :         pszName = "InspireCRS84Quad";
      97             : 
      98         122 :     auto poTM = gdal::TileMatrixSet::parse(pszName);
      99          61 :     if (poTM == nullptr)
     100           1 :         return nullptr;
     101          60 :     if (!poTM->haveAllLevelsSameTopLeft())
     102             :     {
     103           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     104             :                  "Unsupported tiling scheme: not all zoom levels have same top "
     105             :                  "left corner");
     106           0 :         return nullptr;
     107             :     }
     108          60 :     if (!poTM->haveAllLevelsSameTileSize())
     109             :     {
     110           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     111             :                  "Unsupported tiling scheme: not all zoom levels have same "
     112             :                  "tile size");
     113           0 :         return nullptr;
     114             :     }
     115          60 :     if (!poTM->hasOnlyPowerOfTwoVaryingScales())
     116             :     {
     117           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     118             :                  "Unsupported tiling scheme: resolution of consecutive zoom "
     119             :                  "levels is not always 2");
     120           1 :         return nullptr;
     121             :     }
     122          59 :     if (poTM->hasVariableMatrixWidth())
     123             :     {
     124           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     125             :                  "Unsupported tiling scheme: some levels have variable matrix "
     126             :                  "width");
     127           0 :         return nullptr;
     128             :     }
     129         118 :     auto poTilingScheme = std::make_unique<TilingSchemeDefinition>();
     130          59 :     poTilingScheme->pszName = pszName;
     131             : 
     132         118 :     OGRSpatialReference oSRS;
     133          59 :     if (oSRS.SetFromUserInput(poTM->crs().c_str()) != OGRERR_NONE)
     134             :     {
     135           0 :         return nullptr;
     136             :     }
     137          59 :     if (poTM->crs() == "http://www.opengis.net/def/crs/OGC/1.3/CRS84")
     138             :     {
     139           6 :         poTilingScheme->nEPSGCode = 4326;
     140             :     }
     141             :     else
     142             :     {
     143          53 :         const char *pszAuthName = oSRS.GetAuthorityName(nullptr);
     144          53 :         const char *pszAuthCode = oSRS.GetAuthorityCode(nullptr);
     145          53 :         if (pszAuthName == nullptr || !EQUAL(pszAuthName, "EPSG") ||
     146             :             pszAuthCode == nullptr)
     147             :         {
     148           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     149             :                      "Unsupported tiling scheme: only EPSG CRS supported");
     150           0 :             return nullptr;
     151             :         }
     152          53 :         poTilingScheme->nEPSGCode = atoi(pszAuthCode);
     153             :     }
     154          59 :     const auto &zoomLevel0 = poTM->tileMatrixList()[0];
     155          59 :     poTilingScheme->dfMinX = zoomLevel0.mTopLeftX;
     156          59 :     poTilingScheme->dfMaxY = zoomLevel0.mTopLeftY;
     157          59 :     poTilingScheme->nTileXCountZoomLevel0 = zoomLevel0.mMatrixWidth;
     158          59 :     poTilingScheme->nTileYCountZoomLevel0 = zoomLevel0.mMatrixHeight;
     159          59 :     poTilingScheme->nTileWidth = zoomLevel0.mTileWidth;
     160          59 :     poTilingScheme->nTileHeight = zoomLevel0.mTileHeight;
     161          59 :     poTilingScheme->dfPixelXSizeZoomLevel0 = zoomLevel0.mResX;
     162          59 :     poTilingScheme->dfPixelYSizeZoomLevel0 = zoomLevel0.mResY;
     163             : 
     164         118 :     const bool bInvertAxis = oSRS.EPSGTreatsAsLatLong() != FALSE ||
     165          59 :                              oSRS.EPSGTreatsAsNorthingEasting() != FALSE;
     166          59 :     if (bInvertAxis)
     167             :     {
     168           6 :         std::swap(poTilingScheme->dfMinX, poTilingScheme->dfMaxY);
     169           6 :         std::swap(poTilingScheme->dfPixelXSizeZoomLevel0,
     170           6 :                   poTilingScheme->dfPixelYSizeZoomLevel0);
     171             :     }
     172          59 :     return poTilingScheme;
     173             : }
     174             : 
     175             : static const char *pszCREATE_GPKG_GEOMETRY_COLUMNS =
     176             :     "CREATE TABLE gpkg_geometry_columns ("
     177             :     "table_name TEXT NOT NULL,"
     178             :     "column_name TEXT NOT NULL,"
     179             :     "geometry_type_name TEXT NOT NULL,"
     180             :     "srs_id INTEGER NOT NULL,"
     181             :     "z TINYINT NOT NULL,"
     182             :     "m TINYINT NOT NULL,"
     183             :     "CONSTRAINT pk_geom_cols PRIMARY KEY (table_name, column_name),"
     184             :     "CONSTRAINT uk_gc_table_name UNIQUE (table_name),"
     185             :     "CONSTRAINT fk_gc_tn FOREIGN KEY (table_name) REFERENCES "
     186             :     "gpkg_contents(table_name),"
     187             :     "CONSTRAINT fk_gc_srs FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys "
     188             :     "(srs_id)"
     189             :     ")";
     190             : 
     191         992 : OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
     192             : {
     193         992 :     CPLAssert(hDB != nullptr);
     194             : 
     195         992 :     const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
     196             :                                                 "PRAGMA user_version = %u",
     197             :                                                 m_nApplicationId,
     198        1984 :                                                 m_nUserVersion));
     199        1984 :     return SQLCommand(hDB, osPragma.c_str());
     200             : }
     201             : 
     202        2673 : bool GDALGeoPackageDataset::CloseDB()
     203             : {
     204        2673 :     OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
     205        2673 :     m_pSQLFunctionData = nullptr;
     206        2673 :     return OGRSQLiteBaseDataSource::CloseDB();
     207             : }
     208             : 
     209          11 : bool GDALGeoPackageDataset::ReOpenDB()
     210             : {
     211          11 :     CPLAssert(hDB != nullptr);
     212          11 :     CPLAssert(m_pszFilename != nullptr);
     213             : 
     214          11 :     FinishSpatialite();
     215             : 
     216          11 :     CloseDB();
     217             : 
     218             :     /* And re-open the file */
     219          11 :     return OpenOrCreateDB(SQLITE_OPEN_READWRITE);
     220             : }
     221             : 
     222         862 : static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
     223             :                                      int nEPSGCode)
     224             : {
     225         862 :     CPLPushErrorHandler(CPLQuietErrorHandler);
     226         862 :     const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
     227         862 :     CPLPopErrorHandler();
     228         862 :     CPLErrorReset();
     229         862 :     return eErr;
     230             : }
     231             : 
     232             : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
     233        1295 : GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
     234             :                                      bool bEmitErrorIfNotFound)
     235             : {
     236        1295 :     const auto oIter = m_oMapSrsIdToSrs.find(iSrsId);
     237        1295 :     if (oIter != m_oMapSrsIdToSrs.end())
     238             :     {
     239          91 :         if (oIter->second == nullptr)
     240          33 :             return nullptr;
     241          58 :         oIter->second->Reference();
     242             :         return std::unique_ptr<OGRSpatialReference,
     243          58 :                                OGRSpatialReferenceReleaser>(oIter->second);
     244             :     }
     245             : 
     246        1204 :     if (iSrsId == 0 || iSrsId == -1)
     247             :     {
     248         119 :         OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
     249         119 :         poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     250             : 
     251             :         // See corresponding tests in GDALGeoPackageDataset::GetSrsId
     252         119 :         if (iSrsId == 0)
     253             :         {
     254          29 :             poSpatialRef->SetGeogCS("Undefined geographic SRS", "unknown",
     255             :                                     "unknown", SRS_WGS84_SEMIMAJOR,
     256             :                                     SRS_WGS84_INVFLATTENING);
     257             :         }
     258          90 :         else if (iSrsId == -1)
     259             :         {
     260          90 :             poSpatialRef->SetLocalCS("Undefined Cartesian SRS");
     261          90 :             poSpatialRef->SetLinearUnits(SRS_UL_METER, 1.0);
     262             :         }
     263             : 
     264         119 :         m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
     265         119 :         poSpatialRef->Reference();
     266             :         return std::unique_ptr<OGRSpatialReference,
     267         119 :                                OGRSpatialReferenceReleaser>(poSpatialRef);
     268             :     }
     269             : 
     270        2170 :     CPLString oSQL;
     271        1085 :     oSQL.Printf("SELECT srs_name, definition, organization, "
     272             :                 "organization_coordsys_id%s%s "
     273             :                 "FROM gpkg_spatial_ref_sys WHERE "
     274             :                 "srs_id = %d LIMIT 2",
     275        1085 :                 m_bHasDefinition12_063 ? ", definition_12_063" : "",
     276        1085 :                 m_bHasEpochColumn ? ", epoch" : "", iSrsId);
     277             : 
     278        2170 :     auto oResult = SQLQuery(hDB, oSQL.c_str());
     279             : 
     280        1085 :     if (!oResult || oResult->RowCount() != 1)
     281             :     {
     282          12 :         if (bFallbackToEPSG)
     283             :         {
     284           7 :             CPLDebug("GPKG",
     285             :                      "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
     286             :                      iSrsId);
     287           7 :             OGRSpatialReference *poSRS = new OGRSpatialReference();
     288           7 :             if (poSRS->importFromEPSG(iSrsId) == OGRERR_NONE)
     289             :             {
     290           5 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     291             :                 return std::unique_ptr<OGRSpatialReference,
     292           5 :                                        OGRSpatialReferenceReleaser>(poSRS);
     293             :             }
     294           2 :             poSRS->Release();
     295             :         }
     296           5 :         else if (bEmitErrorIfNotFound)
     297             :         {
     298           2 :             CPLError(CE_Warning, CPLE_AppDefined,
     299             :                      "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
     300             :                      iSrsId);
     301           2 :             m_oMapSrsIdToSrs[iSrsId] = nullptr;
     302             :         }
     303           7 :         return nullptr;
     304             :     }
     305             : 
     306        1073 :     const char *pszName = oResult->GetValue(0, 0);
     307        1073 :     if (pszName && EQUAL(pszName, "Undefined SRS"))
     308             :     {
     309         457 :         m_oMapSrsIdToSrs[iSrsId] = nullptr;
     310         457 :         return nullptr;
     311             :     }
     312         616 :     const char *pszWkt = oResult->GetValue(1, 0);
     313         616 :     if (pszWkt == nullptr)
     314           0 :         return nullptr;
     315         616 :     const char *pszOrganization = oResult->GetValue(2, 0);
     316         616 :     const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
     317             :     const char *pszWkt2 =
     318         616 :         m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
     319         616 :     if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
     320          76 :         pszWkt = pszWkt2;
     321             :     const char *pszCoordinateEpoch =
     322         616 :         m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
     323             :     const double dfCoordinateEpoch =
     324         616 :         pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
     325             : 
     326         616 :     OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
     327         616 :     poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     328             :     // Try to import first from EPSG code, and then from WKT
     329         616 :     if (!(pszOrganization && pszOrganizationCoordsysID &&
     330         616 :           EQUAL(pszOrganization, "EPSG") &&
     331         596 :           (atoi(pszOrganizationCoordsysID) == iSrsId ||
     332           4 :            (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
     333         596 :           GDALGPKGImportFromEPSG(
     334        1232 :               poSpatialRef, atoi(pszOrganizationCoordsysID)) == OGRERR_NONE) &&
     335          20 :         poSpatialRef->importFromWkt(pszWkt) != OGRERR_NONE)
     336             :     {
     337           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     338             :                  "Unable to parse srs_id '%d' well-known text '%s'", iSrsId,
     339             :                  pszWkt);
     340           0 :         delete poSpatialRef;
     341           0 :         m_oMapSrsIdToSrs[iSrsId] = nullptr;
     342           0 :         return nullptr;
     343             :     }
     344             : 
     345         616 :     poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
     346         616 :     poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
     347         616 :     m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
     348         616 :     poSpatialRef->Reference();
     349             :     return std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>(
     350         616 :         poSpatialRef);
     351             : }
     352             : 
     353         295 : const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
     354             : {
     355         295 :     const char *pszName = oSRS.GetName();
     356         295 :     if (pszName)
     357         295 :         return pszName;
     358             : 
     359             :     // Something odd.  Return empty.
     360           0 :     return "Unnamed SRS";
     361             : }
     362             : 
     363             : /* Add the definition_12_063 column to an existing gpkg_spatial_ref_sys table */
     364           7 : bool GDALGeoPackageDataset::ConvertGpkgSpatialRefSysToExtensionWkt2(
     365             :     bool bForceEpoch)
     366             : {
     367           7 :     const bool bAddEpoch = (m_nUserVersion >= GPKG_1_4_VERSION || bForceEpoch);
     368             :     auto oResultTable = SQLQuery(
     369             :         hDB, "SELECT srs_name, srs_id, organization, organization_coordsys_id, "
     370          14 :              "definition, description FROM gpkg_spatial_ref_sys LIMIT 100000");
     371           7 :     if (!oResultTable)
     372           0 :         return false;
     373             : 
     374             :     // Temporary remove foreign key checks
     375             :     const GPKGTemporaryForeignKeyCheckDisabler
     376           7 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
     377             : 
     378           7 :     bool bRet = SoftStartTransaction() == OGRERR_NONE;
     379             : 
     380           7 :     if (bRet)
     381             :     {
     382             :         std::string osSQL("CREATE TABLE gpkg_spatial_ref_sys_temp ("
     383             :                           "srs_name TEXT NOT NULL,"
     384             :                           "srs_id INTEGER NOT NULL PRIMARY KEY,"
     385             :                           "organization TEXT NOT NULL,"
     386             :                           "organization_coordsys_id INTEGER NOT NULL,"
     387             :                           "definition TEXT NOT NULL,"
     388             :                           "description TEXT, "
     389           7 :                           "definition_12_063 TEXT NOT NULL");
     390           7 :         if (bAddEpoch)
     391           6 :             osSQL += ", epoch DOUBLE";
     392           7 :         osSQL += ")";
     393           7 :         bRet = SQLCommand(hDB, osSQL.c_str()) == OGRERR_NONE;
     394             :     }
     395             : 
     396           7 :     if (bRet)
     397             :     {
     398          32 :         for (int i = 0; bRet && i < oResultTable->RowCount(); i++)
     399             :         {
     400          25 :             const char *pszSrsName = oResultTable->GetValue(0, i);
     401          25 :             const char *pszSrsId = oResultTable->GetValue(1, i);
     402          25 :             const char *pszOrganization = oResultTable->GetValue(2, i);
     403             :             const char *pszOrganizationCoordsysID =
     404          25 :                 oResultTable->GetValue(3, i);
     405          25 :             const char *pszDefinition = oResultTable->GetValue(4, i);
     406             :             if (pszSrsName == nullptr || pszSrsId == nullptr ||
     407             :                 pszOrganization == nullptr ||
     408             :                 pszOrganizationCoordsysID == nullptr)
     409             :             {
     410             :                 // should not happen as there are NOT NULL constraints
     411             :                 // But a database could lack such NOT NULL constraints or have
     412             :                 // large values that would cause a memory allocation failure.
     413             :             }
     414          25 :             const char *pszDescription = oResultTable->GetValue(5, i);
     415             :             char *pszSQL;
     416             : 
     417          50 :             OGRSpatialReference oSRS;
     418          25 :             if (pszOrganization && pszOrganizationCoordsysID &&
     419          25 :                 EQUAL(pszOrganization, "EPSG"))
     420             :             {
     421           9 :                 oSRS.importFromEPSG(atoi(pszOrganizationCoordsysID));
     422             :             }
     423          34 :             if (!oSRS.IsEmpty() && pszDefinition &&
     424           9 :                 !EQUAL(pszDefinition, "undefined"))
     425             :             {
     426           9 :                 oSRS.SetFromUserInput(pszDefinition);
     427             :             }
     428          25 :             char *pszWKT2 = nullptr;
     429          25 :             if (!oSRS.IsEmpty())
     430             :             {
     431           9 :                 const char *const apszOptionsWkt2[] = {"FORMAT=WKT2_2015",
     432             :                                                        nullptr};
     433           9 :                 oSRS.exportToWkt(&pszWKT2, apszOptionsWkt2);
     434           9 :                 if (pszWKT2 && pszWKT2[0] == '\0')
     435             :                 {
     436           0 :                     CPLFree(pszWKT2);
     437           0 :                     pszWKT2 = nullptr;
     438             :                 }
     439             :             }
     440          25 :             if (pszWKT2 == nullptr)
     441             :             {
     442          16 :                 pszWKT2 = CPLStrdup("undefined");
     443             :             }
     444             : 
     445          25 :             if (pszDescription)
     446             :             {
     447          22 :                 pszSQL = sqlite3_mprintf(
     448             :                     "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
     449             :                     "organization, organization_coordsys_id, definition, "
     450             :                     "description, definition_12_063) VALUES ('%q', '%q', '%q', "
     451             :                     "'%q', '%q', '%q', '%q')",
     452             :                     pszSrsName, pszSrsId, pszOrganization,
     453             :                     pszOrganizationCoordsysID, pszDefinition, pszDescription,
     454             :                     pszWKT2);
     455             :             }
     456             :             else
     457             :             {
     458           3 :                 pszSQL = sqlite3_mprintf(
     459             :                     "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
     460             :                     "organization, organization_coordsys_id, definition, "
     461             :                     "description, definition_12_063) VALUES ('%q', '%q', '%q', "
     462             :                     "'%q', '%q', NULL, '%q')",
     463             :                     pszSrsName, pszSrsId, pszOrganization,
     464             :                     pszOrganizationCoordsysID, pszDefinition, pszWKT2);
     465             :             }
     466             : 
     467          25 :             CPLFree(pszWKT2);
     468          25 :             bRet &= SQLCommand(hDB, pszSQL) == OGRERR_NONE;
     469          25 :             sqlite3_free(pszSQL);
     470             :         }
     471             :     }
     472             : 
     473           7 :     if (bRet)
     474             :     {
     475           7 :         bRet =
     476           7 :             SQLCommand(hDB, "DROP TABLE gpkg_spatial_ref_sys") == OGRERR_NONE;
     477             :     }
     478           7 :     if (bRet)
     479             :     {
     480           7 :         bRet = SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys_temp RENAME "
     481             :                                "TO gpkg_spatial_ref_sys") == OGRERR_NONE;
     482             :     }
     483           7 :     if (bRet)
     484             :     {
     485          14 :         bRet = OGRERR_NONE == CreateExtensionsTableIfNecessary() &&
     486           7 :                OGRERR_NONE == SQLCommand(hDB,
     487             :                                          "INSERT INTO gpkg_extensions "
     488             :                                          "(table_name, column_name, "
     489             :                                          "extension_name, definition, scope) "
     490             :                                          "VALUES "
     491             :                                          "('gpkg_spatial_ref_sys', "
     492             :                                          "'definition_12_063', 'gpkg_crs_wkt', "
     493             :                                          "'http://www.geopackage.org/spec120/"
     494             :                                          "#extension_crs_wkt', 'read-write')");
     495             :     }
     496           7 :     if (bRet && bAddEpoch)
     497             :     {
     498           6 :         bRet =
     499             :             OGRERR_NONE ==
     500           6 :                 SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
     501             :                                 "'gpkg_crs_wkt_1_1' "
     502          12 :                                 "WHERE extension_name = 'gpkg_crs_wkt'") &&
     503             :             OGRERR_NONE ==
     504           6 :                 SQLCommand(
     505             :                     hDB,
     506             :                     "INSERT INTO gpkg_extensions "
     507             :                     "(table_name, column_name, extension_name, definition, "
     508             :                     "scope) "
     509             :                     "VALUES "
     510             :                     "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
     511             :                     "'http://www.geopackage.org/spec/#extension_crs_wkt', "
     512             :                     "'read-write')");
     513             :     }
     514           7 :     if (bRet)
     515             :     {
     516           7 :         SoftCommitTransaction();
     517           7 :         m_bHasDefinition12_063 = true;
     518           7 :         if (bAddEpoch)
     519           6 :             m_bHasEpochColumn = true;
     520             :     }
     521             :     else
     522             :     {
     523           0 :         SoftRollbackTransaction();
     524             :     }
     525             : 
     526           7 :     return bRet;
     527             : }
     528             : 
     529         955 : int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
     530             : {
     531         955 :     const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
     532        1377 :     if (!poSRSIn || poSRSIn->IsEmpty() ||
     533         422 :         (pszName && EQUAL(pszName, "Undefined SRS")))
     534             :     {
     535         535 :         OGRErr err = OGRERR_NONE;
     536         535 :         const int nSRSId = SQLGetInteger(
     537             :             hDB,
     538             :             "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE srs_name = "
     539             :             "'Undefined SRS' AND organization = 'GDAL'",
     540             :             &err);
     541         535 :         if (err == OGRERR_NONE)
     542          57 :             return nSRSId;
     543             : 
     544             :         // The below WKT definitions are somehow questionable (using a unknown
     545             :         // unit). For GDAL >= 3.9, they won't be used. They will only be used
     546             :         // for earlier versions.
     547             :         const char *pszSQL;
     548             : #define UNDEFINED_CRS_SRS_ID 99999
     549             :         static_assert(UNDEFINED_CRS_SRS_ID == FIRST_CUSTOM_SRSID - 1);
     550             : #define STRINGIFY(x) #x
     551             : #define XSTRINGIFY(x) STRINGIFY(x)
     552         478 :         if (m_bHasDefinition12_063)
     553             :         {
     554             :             /* clang-format off */
     555           1 :             pszSQL =
     556             :                 "INSERT INTO gpkg_spatial_ref_sys "
     557             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     558             :                 "definition, definition_12_063, description) VALUES "
     559             :                 "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
     560             :                 XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
     561             :                 "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
     562             :                 "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
     563             :                 "AXIS[\"Northing\",NORTH]]',"
     564             :                 "'ENGCRS[\"Undefined SRS\",EDATUM[\"unknown\"],CS[Cartesian,2],"
     565             :                 "AXIS[\"easting\",east,ORDER[1],LENGTHUNIT[\"unknown\",0]],"
     566             :                 "AXIS[\"northing\",north,ORDER[2],LENGTHUNIT[\"unknown\",0]]]',"
     567             :                 "'Custom undefined coordinate reference system')";
     568             :             /* clang-format on */
     569             :         }
     570             :         else
     571             :         {
     572             :             /* clang-format off */
     573         477 :             pszSQL =
     574             :                 "INSERT INTO gpkg_spatial_ref_sys "
     575             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     576             :                 "definition, description) VALUES "
     577             :                 "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
     578             :                 XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
     579             :                 "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
     580             :                 "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
     581             :                 "AXIS[\"Northing\",NORTH]]',"
     582             :                 "'Custom undefined coordinate reference system')";
     583             :             /* clang-format on */
     584             :         }
     585         478 :         if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
     586         478 :             return UNDEFINED_CRS_SRS_ID;
     587             : #undef UNDEFINED_CRS_SRS_ID
     588             : #undef XSTRINGIFY
     589             : #undef STRINGIFY
     590           0 :         return -1;
     591             :     }
     592             : 
     593         840 :     std::unique_ptr<OGRSpatialReference> poSRS(poSRSIn->Clone());
     594             : 
     595         420 :     if (poSRS->IsGeographic() || poSRS->IsLocal())
     596             :     {
     597             :         // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
     598         145 :         if (pszName != nullptr && strlen(pszName) > 0)
     599             :         {
     600         145 :             if (EQUAL(pszName, "Undefined geographic SRS"))
     601           2 :                 return 0;
     602             : 
     603         143 :             if (EQUAL(pszName, "Undefined Cartesian SRS"))
     604           1 :                 return -1;
     605             :         }
     606             :     }
     607             : 
     608         417 :     const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     609             : 
     610         417 :     if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0)
     611             :     {
     612             :         // Try to force identify an EPSG code.
     613          26 :         poSRS->AutoIdentifyEPSG();
     614             : 
     615          26 :         pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     616          26 :         if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG"))
     617             :         {
     618           0 :             const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
     619           0 :             if (pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0)
     620             :             {
     621             :                 /* Import 'clean' SRS */
     622           0 :                 poSRS->importFromEPSG(atoi(pszAuthorityCode));
     623             : 
     624           0 :                 pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     625             :             }
     626             :         }
     627             : 
     628          26 :         poSRS->SetCoordinateEpoch(poSRSIn->GetCoordinateEpoch());
     629             :     }
     630             : 
     631             :     // Check whether the EPSG authority code is already mapped to a
     632             :     // SRS ID.
     633         417 :     char *pszSQL = nullptr;
     634         417 :     int nSRSId = DEFAULT_SRID;
     635         417 :     int nAuthorityCode = 0;
     636         417 :     OGRErr err = OGRERR_NONE;
     637         417 :     bool bCanUseAuthorityCode = false;
     638         417 :     const char *const apszIsSameOptions[] = {
     639             :         "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
     640             :         "IGNORE_COORDINATE_EPOCH=YES", nullptr};
     641         417 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
     642             :     {
     643         391 :         const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
     644         391 :         if (pszAuthorityCode)
     645             :         {
     646         391 :             if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
     647             :             {
     648         391 :                 nAuthorityCode = atoi(pszAuthorityCode);
     649             :             }
     650             :             else
     651             :             {
     652           0 :                 CPLDebug("GPKG",
     653             :                          "SRS has %s:%s identification, but the code not "
     654             :                          "being an integer value cannot be stored as such "
     655             :                          "in the database.",
     656             :                          pszAuthorityName, pszAuthorityCode);
     657           0 :                 pszAuthorityName = nullptr;
     658             :             }
     659             :         }
     660             :     }
     661             : 
     662         808 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
     663         391 :         poSRSIn->GetCoordinateEpoch() == 0)
     664             :     {
     665             :         pszSQL =
     666         386 :             sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     667             :                             "upper(organization) = upper('%q') AND "
     668             :                             "organization_coordsys_id = %d",
     669             :                             pszAuthorityName, nAuthorityCode);
     670             : 
     671         386 :         nSRSId = SQLGetInteger(hDB, pszSQL, &err);
     672         386 :         sqlite3_free(pszSQL);
     673             : 
     674             :         // Got a match? Return it!
     675         386 :         if (OGRERR_NONE == err)
     676             :         {
     677         118 :             auto poRefSRS = GetSpatialRef(nSRSId);
     678             :             bool bOK =
     679         118 :                 (poRefSRS == nullptr ||
     680         119 :                  poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) ||
     681           1 :                  !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
     682         118 :             if (bOK)
     683             :             {
     684         117 :                 return nSRSId;
     685             :             }
     686             :             else
     687             :             {
     688           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
     689             :                          "Passed SRS uses %s:%d identification, but its "
     690             :                          "definition is not compatible with the "
     691             :                          "definition of that object already in the database. "
     692             :                          "Registering it as a new entry into the database.",
     693             :                          pszAuthorityName, nAuthorityCode);
     694           1 :                 pszAuthorityName = nullptr;
     695           1 :                 nAuthorityCode = 0;
     696             :             }
     697             :         }
     698             :     }
     699             : 
     700             :     // Translate SRS to WKT.
     701         300 :     CPLCharUniquePtr pszWKT1;
     702         300 :     CPLCharUniquePtr pszWKT2_2015;
     703         300 :     CPLCharUniquePtr pszWKT2_2019;
     704         300 :     const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
     705         300 :     const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
     706         300 :     const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
     707             : 
     708         600 :     std::string osEpochTest;
     709         300 :     if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
     710             :     {
     711             :         osEpochTest =
     712           3 :             CPLSPrintf(" AND epoch = %.17g", poSRSIn->GetCoordinateEpoch());
     713             :     }
     714             : 
     715         591 :     if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3) &&
     716         291 :         !poSRS->IsDerivedGeographic())
     717             :     {
     718         582 :         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
     719         291 :         char *pszTmp = nullptr;
     720         291 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
     721         291 :         pszWKT1.reset(pszTmp);
     722         291 :         if (pszWKT1 && pszWKT1.get()[0] == '\0')
     723             :         {
     724           0 :             pszWKT1.reset();
     725             :         }
     726             :     }
     727             :     {
     728         600 :         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
     729         300 :         char *pszTmp = nullptr;
     730         300 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
     731         300 :         pszWKT2_2015.reset(pszTmp);
     732         300 :         if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
     733             :         {
     734           0 :             pszWKT2_2015.reset();
     735             :         }
     736             :     }
     737             :     {
     738         300 :         char *pszTmp = nullptr;
     739         300 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
     740         300 :         pszWKT2_2019.reset(pszTmp);
     741         300 :         if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
     742             :         {
     743           0 :             pszWKT2_2019.reset();
     744             :         }
     745             :     }
     746             : 
     747         300 :     if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
     748             :     {
     749           0 :         return DEFAULT_SRID;
     750             :     }
     751             : 
     752         300 :     if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
     753             :     {
     754             :         // Search if there is already an existing entry with this WKT
     755         297 :         if (m_bHasDefinition12_063 && (pszWKT2_2015 || pszWKT2_2019))
     756             :         {
     757          42 :             if (pszWKT1)
     758             :             {
     759         144 :                 pszSQL = sqlite3_mprintf(
     760             :                     "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     761             :                     "(definition = '%q' OR definition_12_063 IN ('%q','%q'))%s",
     762             :                     pszWKT1.get(),
     763          72 :                     pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
     764          72 :                     pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
     765             :                     osEpochTest.c_str());
     766             :             }
     767             :             else
     768             :             {
     769          24 :                 pszSQL = sqlite3_mprintf(
     770             :                     "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     771             :                     "definition_12_063 IN ('%q', '%q')%s",
     772          12 :                     pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
     773          12 :                     pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
     774             :                     osEpochTest.c_str());
     775             :             }
     776             :         }
     777         255 :         else if (pszWKT1)
     778             :         {
     779             :             pszSQL =
     780         252 :                 sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     781             :                                 "definition = '%q'%s",
     782             :                                 pszWKT1.get(), osEpochTest.c_str());
     783             :         }
     784             :         else
     785             :         {
     786           3 :             pszSQL = nullptr;
     787             :         }
     788         297 :         if (pszSQL)
     789             :         {
     790         294 :             nSRSId = SQLGetInteger(hDB, pszSQL, &err);
     791         294 :             sqlite3_free(pszSQL);
     792         294 :             if (OGRERR_NONE == err)
     793             :             {
     794           5 :                 return nSRSId;
     795             :             }
     796             :         }
     797             :     }
     798             : 
     799         566 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
     800         271 :         poSRSIn->GetCoordinateEpoch() == 0)
     801             :     {
     802         267 :         bool bTryToReuseSRSId = true;
     803         267 :         if (EQUAL(pszAuthorityName, "EPSG"))
     804             :         {
     805         532 :             OGRSpatialReference oSRS_EPSG;
     806         266 :             if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
     807             :                 OGRERR_NONE)
     808             :             {
     809         267 :                 if (!poSRS->IsSame(&oSRS_EPSG, apszIsSameOptions) &&
     810           1 :                     CPLTestBool(
     811             :                         CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")))
     812             :                 {
     813           1 :                     bTryToReuseSRSId = false;
     814           1 :                     CPLError(
     815             :                         CE_Warning, CPLE_AppDefined,
     816             :                         "Passed SRS uses %s:%d identification, but its "
     817             :                         "definition is not compatible with the "
     818             :                         "official definition of the object. "
     819             :                         "Registering it as a non-%s entry into the database.",
     820             :                         pszAuthorityName, nAuthorityCode, pszAuthorityName);
     821           1 :                     pszAuthorityName = nullptr;
     822           1 :                     nAuthorityCode = 0;
     823             :                 }
     824             :             }
     825             :         }
     826         267 :         if (bTryToReuseSRSId)
     827             :         {
     828             :             // No match, but maybe we can use the nAuthorityCode as the nSRSId?
     829         266 :             pszSQL = sqlite3_mprintf(
     830             :                 "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
     831             :                 "srs_id = %d",
     832             :                 nAuthorityCode);
     833             : 
     834             :             // Yep, we can!
     835         266 :             if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
     836         265 :                 bCanUseAuthorityCode = true;
     837         266 :             sqlite3_free(pszSQL);
     838             :         }
     839             :     }
     840             : 
     841         295 :     bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
     842         295 :     bool bForceEpoch = false;
     843         298 :     if (!m_bHasDefinition12_063 && pszWKT1 == nullptr &&
     844           3 :         (pszWKT2_2015 != nullptr || pszWKT2_2019 != nullptr))
     845             :     {
     846           3 :         bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
     847             :     }
     848             : 
     849             :     // Add epoch column if needed
     850         295 :     if (poSRSIn->GetCoordinateEpoch() > 0 && !m_bHasEpochColumn)
     851             :     {
     852           3 :         if (m_bHasDefinition12_063)
     853             :         {
     854           0 :             if (SoftStartTransaction() != OGRERR_NONE)
     855           0 :                 return DEFAULT_SRID;
     856           0 :             if (SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys "
     857           0 :                                 "ADD COLUMN epoch DOUBLE") != OGRERR_NONE ||
     858           0 :                 SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
     859             :                                 "'gpkg_crs_wkt_1_1' "
     860             :                                 "WHERE extension_name = 'gpkg_crs_wkt'") !=
     861           0 :                     OGRERR_NONE ||
     862           0 :                 SQLCommand(
     863             :                     hDB,
     864             :                     "INSERT INTO gpkg_extensions "
     865             :                     "(table_name, column_name, extension_name, definition, "
     866             :                     "scope) "
     867             :                     "VALUES "
     868             :                     "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
     869             :                     "'http://www.geopackage.org/spec/#extension_crs_wkt', "
     870             :                     "'read-write')") != OGRERR_NONE)
     871             :             {
     872           0 :                 SoftRollbackTransaction();
     873           0 :                 return DEFAULT_SRID;
     874             :             }
     875             : 
     876           0 :             if (SoftCommitTransaction() != OGRERR_NONE)
     877           0 :                 return DEFAULT_SRID;
     878             : 
     879           0 :             m_bHasEpochColumn = true;
     880             :         }
     881             :         else
     882             :         {
     883           3 :             bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
     884           3 :             bForceEpoch = true;
     885             :         }
     886             :     }
     887             : 
     888         301 :     if (bConvertGpkgSpatialRefSysToExtensionWkt2 &&
     889           6 :         !ConvertGpkgSpatialRefSysToExtensionWkt2(bForceEpoch))
     890             :     {
     891           0 :         return DEFAULT_SRID;
     892             :     }
     893             : 
     894             :     // Reuse the authority code number as SRS_ID if we can
     895         295 :     if (bCanUseAuthorityCode)
     896             :     {
     897         265 :         nSRSId = nAuthorityCode;
     898             :     }
     899             :     // Otherwise, generate a new SRS_ID number (max + 1)
     900             :     else
     901             :     {
     902             :         // Get the current maximum srid in the srs table.
     903          30 :         const int nMaxSRSId = SQLGetInteger(
     904             :             hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
     905          30 :         nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
     906             :     }
     907             : 
     908         590 :     std::string osEpochColumn;
     909         295 :     std::string osEpochVal;
     910         295 :     if (poSRSIn->GetCoordinateEpoch() > 0)
     911             :     {
     912           5 :         osEpochColumn = ", epoch";
     913           5 :         osEpochVal = CPLSPrintf(", %.17g", poSRSIn->GetCoordinateEpoch());
     914             :     }
     915             : 
     916             :     // Add new SRS row to gpkg_spatial_ref_sys.
     917         295 :     if (m_bHasDefinition12_063)
     918             :     {
     919             :         // Force WKT2_2019 when we have a dynamic CRS and coordinate epoch
     920          45 :         const char *pszWKT2 = poSRSIn->IsDynamic() &&
     921          10 :                                       poSRSIn->GetCoordinateEpoch() > 0 &&
     922           1 :                                       pszWKT2_2019
     923           1 :                                   ? pszWKT2_2019.get()
     924          44 :                               : pszWKT2_2015 ? pszWKT2_2015.get()
     925          97 :                                              : pszWKT2_2019.get();
     926             : 
     927          45 :         if (pszAuthorityName != nullptr && nAuthorityCode > 0)
     928             :         {
     929          99 :             pszSQL = sqlite3_mprintf(
     930             :                 "INSERT INTO gpkg_spatial_ref_sys "
     931             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     932             :                 "definition, definition_12_063%s) VALUES "
     933             :                 "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
     934          33 :                 osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId,
     935             :                 pszAuthorityName, nAuthorityCode,
     936          62 :                 pszWKT1 ? pszWKT1.get() : "undefined",
     937             :                 pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
     938             :         }
     939             :         else
     940             :         {
     941          36 :             pszSQL = sqlite3_mprintf(
     942             :                 "INSERT INTO gpkg_spatial_ref_sys "
     943             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     944             :                 "definition, definition_12_063%s) VALUES "
     945             :                 "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
     946          12 :                 osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId, "NONE",
     947          21 :                 nSRSId, pszWKT1 ? pszWKT1.get() : "undefined",
     948             :                 pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
     949             :         }
     950             :     }
     951             :     else
     952             :     {
     953         250 :         if (pszAuthorityName != nullptr && nAuthorityCode > 0)
     954             :         {
     955         474 :             pszSQL = sqlite3_mprintf(
     956             :                 "INSERT INTO gpkg_spatial_ref_sys "
     957             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     958             :                 "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
     959         237 :                 GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
     960         474 :                 pszWKT1 ? pszWKT1.get() : "undefined");
     961             :         }
     962             :         else
     963             :         {
     964          26 :             pszSQL = sqlite3_mprintf(
     965             :                 "INSERT INTO gpkg_spatial_ref_sys "
     966             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     967             :                 "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
     968          13 :                 GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
     969          26 :                 pszWKT1 ? pszWKT1.get() : "undefined");
     970             :         }
     971             :     }
     972             : 
     973             :     // Add new row to gpkg_spatial_ref_sys.
     974         295 :     CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
     975             : 
     976             :     // Free everything that was allocated.
     977         295 :     sqlite3_free(pszSQL);
     978             : 
     979         295 :     return nSRSId;
     980             : }
     981             : 
     982             : /************************************************************************/
     983             : /*                       ~GDALGeoPackageDataset()                       */
     984             : /************************************************************************/
     985             : 
     986        5324 : GDALGeoPackageDataset::~GDALGeoPackageDataset()
     987             : {
     988        2662 :     GDALGeoPackageDataset::Close();
     989        5324 : }
     990             : 
     991             : /************************************************************************/
     992             : /*                               Close()                                */
     993             : /************************************************************************/
     994             : 
     995        4477 : CPLErr GDALGeoPackageDataset::Close(GDALProgressFunc, void *)
     996             : {
     997        4477 :     CPLErr eErr = CE_None;
     998        4477 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
     999             :     {
    1000        1562 :         if (eAccess == GA_Update && m_poParentDS == nullptr &&
    1001        4224 :             !m_osRasterTable.empty() && !m_bGeoTransformValid)
    1002             :         {
    1003           3 :             CPLError(CE_Failure, CPLE_AppDefined,
    1004             :                      "Raster table %s not correctly initialized due to missing "
    1005             :                      "call to SetGeoTransform()",
    1006             :                      m_osRasterTable.c_str());
    1007             :         }
    1008             : 
    1009        5313 :         if (!IsMarkedSuppressOnClose() &&
    1010        2651 :             GDALGeoPackageDataset::FlushCache(true) != CE_None)
    1011             :         {
    1012           7 :             eErr = CE_Failure;
    1013             :         }
    1014             : 
    1015             :         // Destroy bands now since we don't want
    1016             :         // GDALGPKGMBTilesLikeRasterBand::FlushCache() to run after dataset
    1017             :         // destruction
    1018        4484 :         for (int i = 0; i < nBands; i++)
    1019        1822 :             delete papoBands[i];
    1020        2662 :         nBands = 0;
    1021        2662 :         CPLFree(papoBands);
    1022        2662 :         papoBands = nullptr;
    1023             : 
    1024             :         // Destroy overviews before cleaning m_hTempDB as they could still
    1025             :         // need it
    1026        2662 :         m_apoOverviewDS.clear();
    1027             : 
    1028        2662 :         if (m_poParentDS)
    1029             :         {
    1030         325 :             hDB = nullptr;
    1031             :         }
    1032             : 
    1033        2662 :         m_apoLayers.clear();
    1034             : 
    1035             :         std::map<int, OGRSpatialReference *>::iterator oIter =
    1036        2662 :             m_oMapSrsIdToSrs.begin();
    1037        3856 :         for (; oIter != m_oMapSrsIdToSrs.end(); ++oIter)
    1038             :         {
    1039        1194 :             OGRSpatialReference *poSRS = oIter->second;
    1040        1194 :             if (poSRS)
    1041         735 :                 poSRS->Release();
    1042             :         }
    1043             : 
    1044        2662 :         if (!CloseDB())
    1045           0 :             eErr = CE_Failure;
    1046             : 
    1047        2662 :         if (OGRSQLiteBaseDataSource::Close() != CE_None)
    1048           0 :             eErr = CE_Failure;
    1049             :     }
    1050        4477 :     return eErr;
    1051             : }
    1052             : 
    1053             : /************************************************************************/
    1054             : /*                          ICanIWriteBlock()                           */
    1055             : /************************************************************************/
    1056             : 
    1057        5696 : bool GDALGeoPackageDataset::ICanIWriteBlock()
    1058             : {
    1059        5696 :     if (!GetUpdate())
    1060             :     {
    1061           0 :         CPLError(
    1062             :             CE_Failure, CPLE_NotSupported,
    1063             :             "IWriteBlock() not supported on dataset opened in read-only mode");
    1064           0 :         return false;
    1065             :     }
    1066             : 
    1067        5696 :     if (m_pabyCachedTiles == nullptr)
    1068             :     {
    1069           0 :         return false;
    1070             :     }
    1071             : 
    1072        5696 :     if (!m_bGeoTransformValid || m_nSRID == UNKNOWN_SRID)
    1073             :     {
    1074           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1075             :                  "IWriteBlock() not supported if georeferencing not set");
    1076           0 :         return false;
    1077             :     }
    1078        5696 :     return true;
    1079             : }
    1080             : 
    1081             : /************************************************************************/
    1082             : /*                             IRasterIO()                              */
    1083             : /************************************************************************/
    1084             : 
    1085         134 : CPLErr GDALGeoPackageDataset::IRasterIO(
    1086             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
    1087             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
    1088             :     int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
    1089             :     GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
    1090             : 
    1091             : {
    1092         134 :     CPLErr eErr = OGRSQLiteBaseDataSource::IRasterIO(
    1093             :         eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    1094             :         eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
    1095             :         psExtraArg);
    1096             : 
    1097             :     // If writing all bands, in non-shifted mode, flush all entirely written
    1098             :     // tiles This can avoid "stressing" the block cache with too many dirty
    1099             :     // blocks. Note: this logic would be useless with a per-dataset block cache.
    1100         134 :     if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
    1101         123 :         nYSize == nBufYSize && nBandCount == nBands &&
    1102         120 :         m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
    1103             :     {
    1104             :         auto poBand =
    1105         116 :             cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
    1106             :         int nBlockXSize, nBlockYSize;
    1107         116 :         poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
    1108         116 :         const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
    1109         116 :         const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
    1110         116 :         const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
    1111         116 :         const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
    1112         270 :         for (int nBlockY = nBlockXStart; nBlockY < nBlockYEnd; nBlockY++)
    1113             :         {
    1114        4371 :             for (int nBlockX = nBlockYStart; nBlockX < nBlockXEnd; nBlockX++)
    1115             :             {
    1116             :                 GDALRasterBlock *poBlock =
    1117        4217 :                     poBand->AccessibleTryGetLockedBlockRef(nBlockX, nBlockY);
    1118        4217 :                 if (poBlock)
    1119             :                 {
    1120             :                     // GetDirty() should be true in most situation (otherwise
    1121             :                     // it means the block cache is under extreme pressure!)
    1122        4215 :                     if (poBlock->GetDirty())
    1123             :                     {
    1124             :                         // IWriteBlock() on one band will check the dirty state
    1125             :                         // of the corresponding blocks in other bands, to decide
    1126             :                         // if it can call WriteTile(), so we have only to do
    1127             :                         // that on one of the bands
    1128        4215 :                         if (poBlock->Write() != CE_None)
    1129         250 :                             eErr = CE_Failure;
    1130             :                     }
    1131        4215 :                     poBlock->DropLock();
    1132             :                 }
    1133             :             }
    1134             :         }
    1135             :     }
    1136             : 
    1137         134 :     return eErr;
    1138             : }
    1139             : 
    1140             : /************************************************************************/
    1141             : /*                          GetOGRTableLimit()                          */
    1142             : /************************************************************************/
    1143             : 
    1144        4334 : static int GetOGRTableLimit()
    1145             : {
    1146        4334 :     return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
    1147             : }
    1148             : 
    1149             : /************************************************************************/
    1150             : /*                   GetNameTypeMapFromSQliteMaster()                   */
    1151             : /************************************************************************/
    1152             : 
    1153             : const std::map<CPLString, CPLString> &
    1154        1336 : GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
    1155             : {
    1156        1336 :     if (!m_oMapNameToType.empty())
    1157         349 :         return m_oMapNameToType;
    1158             : 
    1159             :     CPLString osSQL(
    1160             :         "SELECT name, type FROM sqlite_master WHERE "
    1161             :         "type IN ('view', 'table') OR "
    1162        1974 :         "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
    1163         987 :     const int nTableLimit = GetOGRTableLimit();
    1164         987 :     if (nTableLimit > 0)
    1165             :     {
    1166         987 :         osSQL += " LIMIT ";
    1167         987 :         osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
    1168             :     }
    1169             : 
    1170         987 :     auto oResult = SQLQuery(hDB, osSQL);
    1171         987 :     if (oResult)
    1172             :     {
    1173       16377 :         for (int i = 0; i < oResult->RowCount(); i++)
    1174             :         {
    1175       15390 :             const char *pszName = oResult->GetValue(0, i);
    1176       15390 :             const char *pszType = oResult->GetValue(1, i);
    1177       15390 :             m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
    1178             :         }
    1179             :     }
    1180             : 
    1181         987 :     return m_oMapNameToType;
    1182             : }
    1183             : 
    1184             : /************************************************************************/
    1185             : /*                  RemoveTableFromSQLiteMasterCache()                  */
    1186             : /************************************************************************/
    1187             : 
    1188          57 : void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
    1189             :     const char *pszTableName)
    1190             : {
    1191          57 :     m_oMapNameToType.erase(CPLString(pszTableName).toupper());
    1192          57 : }
    1193             : 
    1194             : /************************************************************************/
    1195             : /*                 GetUnknownExtensionsTableSpecific()                  */
    1196             : /************************************************************************/
    1197             : 
    1198             : const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
    1199         938 : GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
    1200             : {
    1201         938 :     if (m_bMapTableToExtensionsBuilt)
    1202          92 :         return m_oMapTableToExtensions;
    1203         846 :     m_bMapTableToExtensionsBuilt = true;
    1204             : 
    1205         846 :     if (!HasExtensionsTable())
    1206          52 :         return m_oMapTableToExtensions;
    1207             : 
    1208             :     CPLString osSQL(
    1209             :         "SELECT table_name, extension_name, definition, scope "
    1210             :         "FROM gpkg_extensions WHERE "
    1211             :         "table_name IS NOT NULL "
    1212             :         "AND extension_name IS NOT NULL "
    1213             :         "AND definition IS NOT NULL "
    1214             :         "AND scope IS NOT NULL "
    1215             :         "AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
    1216             :         "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', "
    1217             :         "'gpkg_geom_MULTICURVE', "
    1218             :         "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
    1219             :         "'gpkg_geom_POLYHEDRALSURFACE', 'gpkg_geom_TIN', 'gpkg_geom_TRIANGLE', "
    1220             :         "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', "
    1221             :         "'gpkg_srs_id_trigger', "
    1222             :         "'gpkg_crs_wkt', 'gpkg_crs_wkt_1_1', 'gpkg_schema', "
    1223             :         "'gpkg_related_tables', 'related_tables'"
    1224             : #ifdef HAVE_SPATIALITE
    1225             :         ", 'gdal_spatialite_computed_geom_column'"
    1226             : #endif
    1227        1588 :         ")");
    1228         794 :     const int nTableLimit = GetOGRTableLimit();
    1229         794 :     if (nTableLimit > 0)
    1230             :     {
    1231         794 :         osSQL += " LIMIT ";
    1232         794 :         osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
    1233             :     }
    1234             : 
    1235         794 :     auto oResult = SQLQuery(hDB, osSQL);
    1236         794 :     if (oResult)
    1237             :     {
    1238        1467 :         for (int i = 0; i < oResult->RowCount(); i++)
    1239             :         {
    1240         673 :             const char *pszTableName = oResult->GetValue(0, i);
    1241         673 :             const char *pszExtensionName = oResult->GetValue(1, i);
    1242         673 :             const char *pszDefinition = oResult->GetValue(2, i);
    1243         673 :             const char *pszScope = oResult->GetValue(3, i);
    1244         673 :             if (pszTableName && pszExtensionName && pszDefinition && pszScope)
    1245             :             {
    1246         673 :                 GPKGExtensionDesc oDesc;
    1247         673 :                 oDesc.osExtensionName = pszExtensionName;
    1248         673 :                 oDesc.osDefinition = pszDefinition;
    1249         673 :                 oDesc.osScope = pszScope;
    1250        1346 :                 m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
    1251         673 :                     .push_back(std::move(oDesc));
    1252             :             }
    1253             :         }
    1254             :     }
    1255             : 
    1256         794 :     return m_oMapTableToExtensions;
    1257             : }
    1258             : 
    1259             : /************************************************************************/
    1260             : /*                            GetContents()                             */
    1261             : /************************************************************************/
    1262             : 
    1263             : const std::map<CPLString, GPKGContentsDesc> &
    1264         920 : GDALGeoPackageDataset::GetContents()
    1265             : {
    1266         920 :     if (m_bMapTableToContentsBuilt)
    1267          76 :         return m_oMapTableToContents;
    1268         844 :     m_bMapTableToContentsBuilt = true;
    1269             : 
    1270             :     CPLString osSQL("SELECT table_name, data_type, identifier, "
    1271             :                     "description, min_x, min_y, max_x, max_y "
    1272        1688 :                     "FROM gpkg_contents");
    1273         844 :     const int nTableLimit = GetOGRTableLimit();
    1274         844 :     if (nTableLimit > 0)
    1275             :     {
    1276         844 :         osSQL += " LIMIT ";
    1277         844 :         osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1278             :     }
    1279             : 
    1280         844 :     auto oResult = SQLQuery(hDB, osSQL);
    1281         844 :     if (oResult)
    1282             :     {
    1283        1813 :         for (int i = 0; i < oResult->RowCount(); i++)
    1284             :         {
    1285         969 :             const char *pszTableName = oResult->GetValue(0, i);
    1286         969 :             if (pszTableName == nullptr)
    1287           0 :                 continue;
    1288         969 :             const char *pszDataType = oResult->GetValue(1, i);
    1289         969 :             const char *pszIdentifier = oResult->GetValue(2, i);
    1290         969 :             const char *pszDescription = oResult->GetValue(3, i);
    1291         969 :             const char *pszMinX = oResult->GetValue(4, i);
    1292         969 :             const char *pszMinY = oResult->GetValue(5, i);
    1293         969 :             const char *pszMaxX = oResult->GetValue(6, i);
    1294         969 :             const char *pszMaxY = oResult->GetValue(7, i);
    1295         969 :             GPKGContentsDesc oDesc;
    1296         969 :             if (pszDataType)
    1297         969 :                 oDesc.osDataType = pszDataType;
    1298         969 :             if (pszIdentifier)
    1299         969 :                 oDesc.osIdentifier = pszIdentifier;
    1300         969 :             if (pszDescription)
    1301         968 :                 oDesc.osDescription = pszDescription;
    1302         969 :             if (pszMinX)
    1303         656 :                 oDesc.osMinX = pszMinX;
    1304         969 :             if (pszMinY)
    1305         656 :                 oDesc.osMinY = pszMinY;
    1306         969 :             if (pszMaxX)
    1307         656 :                 oDesc.osMaxX = pszMaxX;
    1308         969 :             if (pszMaxY)
    1309         656 :                 oDesc.osMaxY = pszMaxY;
    1310        1938 :             m_oMapTableToContents[CPLString(pszTableName).toupper()] =
    1311        1938 :                 std::move(oDesc);
    1312             :         }
    1313             :     }
    1314             : 
    1315         844 :     return m_oMapTableToContents;
    1316             : }
    1317             : 
    1318             : /************************************************************************/
    1319             : /*                                Open()                                */
    1320             : /************************************************************************/
    1321             : 
    1322        1317 : int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
    1323             :                                 const std::string &osFilenameInZip)
    1324             : {
    1325        1317 :     m_osFilenameInZip = osFilenameInZip;
    1326        1317 :     CPLAssert(m_apoLayers.empty());
    1327        1317 :     CPLAssert(hDB == nullptr);
    1328             : 
    1329        1317 :     SetDescription(poOpenInfo->pszFilename);
    1330        2634 :     CPLString osFilename(poOpenInfo->pszFilename);
    1331        2634 :     CPLString osSubdatasetTableName;
    1332             :     GByte abyHeaderLetMeHerePlease[100];
    1333        1317 :     const GByte *pabyHeader = poOpenInfo->pabyHeader;
    1334        1317 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
    1335             :     {
    1336         248 :         char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
    1337             :                                                 CSLT_HONOURSTRINGS);
    1338         248 :         int nCount = CSLCount(papszTokens);
    1339         248 :         if (nCount < 2)
    1340             :         {
    1341           0 :             CSLDestroy(papszTokens);
    1342           0 :             return FALSE;
    1343             :         }
    1344             : 
    1345         248 :         if (nCount <= 3)
    1346             :         {
    1347         246 :             osFilename = papszTokens[1];
    1348             :         }
    1349             :         /* GPKG:C:\BLA.GPKG:foo */
    1350           2 :         else if (nCount == 4 && strlen(papszTokens[1]) == 1 &&
    1351           2 :                  (papszTokens[2][0] == '/' || papszTokens[2][0] == '\\'))
    1352             :         {
    1353           2 :             osFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
    1354             :         }
    1355             :         // GPKG:/vsicurl/http[s]://[user:passwd@]example.com[:8080]/foo.gpkg:bar
    1356           0 :         else if (/*nCount >= 4 && */
    1357           0 :                  (EQUAL(papszTokens[1], "/vsicurl/http") ||
    1358           0 :                   EQUAL(papszTokens[1], "/vsicurl/https")))
    1359             :         {
    1360           0 :             osFilename = CPLString(papszTokens[1]);
    1361           0 :             for (int i = 2; i < nCount - 1; i++)
    1362             :             {
    1363           0 :                 osFilename += ':';
    1364           0 :                 osFilename += papszTokens[i];
    1365             :             }
    1366             :         }
    1367         248 :         if (nCount >= 3)
    1368          14 :             osSubdatasetTableName = papszTokens[nCount - 1];
    1369             : 
    1370         248 :         CSLDestroy(papszTokens);
    1371         248 :         VSILFILE *fp = VSIFOpenL(osFilename, "rb");
    1372         248 :         if (fp != nullptr)
    1373             :         {
    1374         248 :             VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
    1375         248 :             VSIFCloseL(fp);
    1376             :         }
    1377         248 :         pabyHeader = abyHeaderLetMeHerePlease;
    1378             :     }
    1379        1069 :     else if (poOpenInfo->pabyHeader &&
    1380        1069 :              STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1381             :                          "SQLite format 3"))
    1382             :     {
    1383        1062 :         m_bCallUndeclareFileNotToOpen = true;
    1384        1062 :         GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
    1385             :                                          poOpenInfo->nHeaderBytes);
    1386             :     }
    1387             : 
    1388        1317 :     eAccess = poOpenInfo->eAccess;
    1389        1317 :     if (!m_osFilenameInZip.empty())
    1390             :     {
    1391           2 :         m_pszFilename = CPLStrdup(CPLSPrintf(
    1392             :             "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
    1393             :     }
    1394             :     else
    1395             :     {
    1396        1315 :         m_pszFilename = CPLStrdup(osFilename);
    1397             :     }
    1398             : 
    1399        1317 :     if (poOpenInfo->papszOpenOptions)
    1400             :     {
    1401         100 :         CSLDestroy(papszOpenOptions);
    1402         100 :         papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    1403             :     }
    1404             : 
    1405             : #ifdef ENABLE_SQL_GPKG_FORMAT
    1406        1317 :     if (poOpenInfo->pabyHeader &&
    1407        1069 :         STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1408           5 :                     "-- SQL GPKG") &&
    1409           5 :         poOpenInfo->fpL != nullptr)
    1410             :     {
    1411           5 :         if (sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr) !=
    1412             :             SQLITE_OK)
    1413             :         {
    1414           0 :             return FALSE;
    1415             :         }
    1416             : 
    1417           5 :         InstallSQLFunctions();
    1418             : 
    1419             :         // Ingest the lines of the dump
    1420           5 :         VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
    1421             :         const char *pszLine;
    1422          76 :         while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
    1423             :         {
    1424          71 :             if (STARTS_WITH(pszLine, "--"))
    1425           5 :                 continue;
    1426             : 
    1427          66 :             if (!SQLCheckLineIsSafe(pszLine))
    1428           0 :                 return false;
    1429             : 
    1430          66 :             char *pszErrMsg = nullptr;
    1431          66 :             if (sqlite3_exec(hDB, pszLine, nullptr, nullptr, &pszErrMsg) !=
    1432             :                 SQLITE_OK)
    1433             :             {
    1434           0 :                 if (pszErrMsg)
    1435           0 :                     CPLDebug("SQLITE", "Error %s", pszErrMsg);
    1436             :             }
    1437          66 :             sqlite3_free(pszErrMsg);
    1438           5 :         }
    1439             :     }
    1440             : 
    1441        1312 :     else if (pabyHeader != nullptr)
    1442             : #endif
    1443             :     {
    1444        1312 :         if (poOpenInfo->fpL)
    1445             :         {
    1446             :             // See above comment about -wal locking for the importance of
    1447             :             // closing that file, prior to calling sqlite3_open()
    1448         964 :             VSIFCloseL(poOpenInfo->fpL);
    1449         964 :             poOpenInfo->fpL = nullptr;
    1450             :         }
    1451             : 
    1452             :         /* See if we can open the SQLite database */
    1453        1312 :         if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
    1454             :                                         : SQLITE_OPEN_READONLY))
    1455           2 :             return FALSE;
    1456             : 
    1457        1310 :         memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
    1458        1310 :         m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
    1459        1310 :         memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
    1460        1310 :         m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
    1461        1310 :         if (m_nApplicationId == GP10_APPLICATION_ID)
    1462             :         {
    1463           7 :             CPLDebug("GPKG", "GeoPackage v1.0");
    1464             :         }
    1465        1303 :         else if (m_nApplicationId == GP11_APPLICATION_ID)
    1466             :         {
    1467           2 :             CPLDebug("GPKG", "GeoPackage v1.1");
    1468             :         }
    1469        1301 :         else if (m_nApplicationId == GPKG_APPLICATION_ID &&
    1470        1297 :                  m_nUserVersion >= GPKG_1_2_VERSION)
    1471             :         {
    1472        1295 :             CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
    1473        1295 :                      (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
    1474             :         }
    1475             :     }
    1476             : 
    1477             :     /* Requirement 6: The SQLite PRAGMA integrity_check SQL command SHALL return
    1478             :      * “ok” */
    1479             :     /* http://opengis.github.io/geopackage/#_file_integrity */
    1480             :     /* Disable integrity check by default, since it is expensive on big files */
    1481        1315 :     if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_INTEGRITY_CHECK", "NO")) &&
    1482           0 :         OGRERR_NONE != PragmaCheck("integrity_check", "ok", 1))
    1483             :     {
    1484           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1485             :                  "pragma integrity_check on '%s' failed", m_pszFilename);
    1486           0 :         return FALSE;
    1487             :     }
    1488             : 
    1489             :     /* Requirement 7: The SQLite PRAGMA foreign_key_check() SQL with no */
    1490             :     /* parameter value SHALL return an empty result set */
    1491             :     /* http://opengis.github.io/geopackage/#_file_integrity */
    1492             :     /* Disable the check by default, since it is to corrupt databases, and */
    1493             :     /* that causes issues to downstream software that can't open them. */
    1494        1315 :     if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_FOREIGN_KEY_CHECK", "NO")) &&
    1495           0 :         OGRERR_NONE != PragmaCheck("foreign_key_check", "", 0))
    1496             :     {
    1497           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1498             :                  "pragma foreign_key_check on '%s' failed.", m_pszFilename);
    1499           0 :         return FALSE;
    1500             :     }
    1501             : 
    1502             :     /* Check for requirement metadata tables */
    1503             :     /* Requirement 10: gpkg_spatial_ref_sys must exist */
    1504             :     /* Requirement 13: gpkg_contents must exist */
    1505        1315 :     if (SQLGetInteger(hDB,
    1506             :                       "SELECT COUNT(*) FROM sqlite_master WHERE "
    1507             :                       "name IN ('gpkg_spatial_ref_sys', 'gpkg_contents') AND "
    1508             :                       "type IN ('table', 'view')",
    1509        1315 :                       nullptr) != 2)
    1510             :     {
    1511           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1512             :                  "At least one of the required GeoPackage tables, "
    1513             :                  "gpkg_spatial_ref_sys or gpkg_contents, is missing");
    1514           0 :         return FALSE;
    1515             :     }
    1516             : 
    1517        1315 :     DetectSpatialRefSysColumns();
    1518             : 
    1519             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    1520        1315 :     if (SQLGetInteger(hDB,
    1521             :                       "SELECT 1 FROM sqlite_master WHERE "
    1522             :                       "name = 'gpkg_ogr_contents' AND type = 'table'",
    1523        1315 :                       nullptr) == 1)
    1524             :     {
    1525        1307 :         m_bHasGPKGOGRContents = true;
    1526             :     }
    1527             : #endif
    1528             : 
    1529        1315 :     CheckUnknownExtensions();
    1530             : 
    1531        1315 :     int bRet = FALSE;
    1532        1315 :     bool bHasGPKGExtRelations = false;
    1533        1315 :     if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
    1534             :     {
    1535        1128 :         m_bHasGPKGGeometryColumns =
    1536        1128 :             SQLGetInteger(hDB,
    1537             :                           "SELECT 1 FROM sqlite_master WHERE "
    1538             :                           "name = 'gpkg_geometry_columns' AND "
    1539             :                           "type IN ('table', 'view')",
    1540        1128 :                           nullptr) == 1;
    1541        1128 :         bHasGPKGExtRelations = HasGpkgextRelationsTable();
    1542             :     }
    1543        1315 :     if (m_bHasGPKGGeometryColumns)
    1544             :     {
    1545             :         /* Load layer definitions for all tables in gpkg_contents &
    1546             :          * gpkg_geometry_columns */
    1547             :         /* and non-spatial tables as well */
    1548             :         std::string osSQL =
    1549             :             "SELECT c.table_name, c.identifier, 1 as is_spatial, "
    1550             :             "g.column_name, g.geometry_type_name, g.z, g.m, c.min_x, c.min_y, "
    1551             :             "c.max_x, c.max_y, 1 AS is_in_gpkg_contents, "
    1552             :             "(SELECT type FROM sqlite_master WHERE lower(name) = "
    1553             :             "lower(c.table_name) AND type IN ('table', 'view')) AS object_type "
    1554             :             "  FROM gpkg_geometry_columns g "
    1555             :             "  JOIN gpkg_contents c ON (g.table_name = c.table_name)"
    1556             :             "  WHERE "
    1557             :             "  c.table_name <> 'ogr_empty_table' AND"
    1558             :             "  c.data_type = 'features' "
    1559             :             // aspatial: Was the only method available in OGR 2.0 and 2.1
    1560             :             // attributes: GPKG 1.2 or later
    1561             :             "UNION ALL "
    1562             :             "SELECT table_name, identifier, 0 as is_spatial, NULL, NULL, 0, 0, "
    1563             :             "0 AS xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 1 AS "
    1564             :             "is_in_gpkg_contents, "
    1565             :             "(SELECT type FROM sqlite_master WHERE lower(name) = "
    1566             :             "lower(table_name) AND type IN ('table', 'view')) AS object_type "
    1567             :             "  FROM gpkg_contents"
    1568        1127 :             "  WHERE data_type IN ('aspatial', 'attributes') ";
    1569             : 
    1570        2254 :         const char *pszListAllTables = CSLFetchNameValueDef(
    1571        1127 :             poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
    1572        1127 :         bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
    1573        1127 :         if (!bHasASpatialOrAttributes)
    1574             :         {
    1575             :             auto oResultTable =
    1576             :                 SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
    1577        1126 :                               "data_type = 'attributes' LIMIT 1");
    1578        1126 :             bHasASpatialOrAttributes =
    1579        1126 :                 (oResultTable && oResultTable->RowCount() == 1);
    1580             :         }
    1581        1127 :         if (bHasGPKGExtRelations)
    1582             :         {
    1583             :             osSQL += "UNION ALL "
    1584             :                      "SELECT mapping_table_name, mapping_table_name, 0 as "
    1585             :                      "is_spatial, NULL, NULL, 0, 0, 0 AS "
    1586             :                      "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
    1587             :                      "is_in_gpkg_contents, 'table' AS object_type "
    1588             :                      "FROM gpkgext_relations WHERE "
    1589             :                      "lower(mapping_table_name) NOT IN (SELECT "
    1590             :                      "lower(table_name) FROM gpkg_contents) AND "
    1591             :                      "EXISTS (SELECT 1 FROM sqlite_master WHERE "
    1592             :                      "type IN ('table', 'view') AND "
    1593          18 :                      "lower(name) = lower(mapping_table_name))";
    1594             :         }
    1595        1127 :         if (EQUAL(pszListAllTables, "YES") ||
    1596        1126 :             (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
    1597             :         {
    1598             :             // vgpkg_ is Spatialite virtual table
    1599             :             osSQL +=
    1600             :                 "UNION ALL "
    1601             :                 "SELECT name, name, 0 as is_spatial, NULL, NULL, 0, 0, 0 AS "
    1602             :                 "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
    1603             :                 "is_in_gpkg_contents, type AS object_type "
    1604             :                 "FROM sqlite_master WHERE type IN ('table', 'view') "
    1605             :                 "AND name NOT LIKE 'gpkg_%' "
    1606             :                 "AND name NOT LIKE 'vgpkg_%' "
    1607             :                 "AND name NOT LIKE 'rtree_%' AND name NOT LIKE 'sqlite_%' "
    1608             :                 // Avoid reading those views from simple_sewer_features.gpkg
    1609             :                 "AND name NOT IN ('st_spatial_ref_sys', 'spatial_ref_sys', "
    1610             :                 "'st_geometry_columns', 'geometry_columns') "
    1611             :                 "AND lower(name) NOT IN (SELECT lower(table_name) FROM "
    1612        1057 :                 "gpkg_contents)";
    1613        1057 :             if (bHasGPKGExtRelations)
    1614             :             {
    1615             :                 osSQL += " AND lower(name) NOT IN (SELECT "
    1616             :                          "lower(mapping_table_name) FROM "
    1617          13 :                          "gpkgext_relations)";
    1618             :             }
    1619             :         }
    1620        1127 :         const int nTableLimit = GetOGRTableLimit();
    1621        1127 :         if (nTableLimit > 0)
    1622             :         {
    1623        1127 :             osSQL += " LIMIT ";
    1624        1127 :             osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1625             :         }
    1626             : 
    1627        1127 :         auto oResult = SQLQuery(hDB, osSQL.c_str());
    1628        1127 :         if (!oResult)
    1629             :         {
    1630           0 :             return FALSE;
    1631             :         }
    1632             : 
    1633        1127 :         if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
    1634             :         {
    1635           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1636             :                      "File has more than %d vector tables. "
    1637             :                      "Limiting to first %d (can be overridden with "
    1638             :                      "OGR_TABLE_LIMIT config option)",
    1639             :                      nTableLimit, nTableLimit);
    1640           1 :             oResult->LimitRowCount(nTableLimit);
    1641             :         }
    1642             : 
    1643        1127 :         if (oResult->RowCount() > 0)
    1644             :         {
    1645        1010 :             bRet = TRUE;
    1646             : 
    1647        1010 :             m_apoLayers.reserve(oResult->RowCount());
    1648             : 
    1649        2020 :             std::map<std::string, int> oMapTableRefCount;
    1650        4219 :             for (int i = 0; i < oResult->RowCount(); i++)
    1651             :             {
    1652        3209 :                 const char *pszTableName = oResult->GetValue(0, i);
    1653        3209 :                 if (pszTableName == nullptr)
    1654           0 :                     continue;
    1655        3209 :                 if (++oMapTableRefCount[pszTableName] == 2)
    1656             :                 {
    1657             :                     // This should normally not happen if all constraints are
    1658             :                     // properly set
    1659           2 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1660             :                              "Table %s appearing several times in "
    1661             :                              "gpkg_contents and/or gpkg_geometry_columns",
    1662             :                              pszTableName);
    1663             :                 }
    1664             :             }
    1665             : 
    1666        2020 :             std::set<std::string> oExistingLayers;
    1667        4219 :             for (int i = 0; i < oResult->RowCount(); i++)
    1668             :             {
    1669        3209 :                 const char *pszTableName = oResult->GetValue(0, i);
    1670        3209 :                 if (pszTableName == nullptr)
    1671           2 :                     continue;
    1672             :                 const bool bTableHasSeveralGeomColumns =
    1673        3209 :                     oMapTableRefCount[pszTableName] > 1;
    1674        3209 :                 bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
    1675        3209 :                 const char *pszGeomColName = oResult->GetValue(3, i);
    1676        3209 :                 const char *pszGeomType = oResult->GetValue(4, i);
    1677        3209 :                 const char *pszZ = oResult->GetValue(5, i);
    1678        3209 :                 const char *pszM = oResult->GetValue(6, i);
    1679             :                 bool bIsInGpkgContents =
    1680        3209 :                     CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
    1681        3209 :                 if (!bIsInGpkgContents)
    1682          44 :                     m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
    1683        3209 :                 const char *pszObjectType = oResult->GetValue(12, i);
    1684        3209 :                 if (pszObjectType == nullptr ||
    1685        3208 :                     !(EQUAL(pszObjectType, "table") ||
    1686          21 :                       EQUAL(pszObjectType, "view")))
    1687             :                 {
    1688           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1689             :                              "Table/view %s is referenced in gpkg_contents, "
    1690             :                              "but does not exist",
    1691             :                              pszTableName);
    1692           1 :                     continue;
    1693             :                 }
    1694             :                 // Non-standard and undocumented behavior:
    1695             :                 // if the same table appears to have several geometry columns,
    1696             :                 // handle it for now as multiple layers named
    1697             :                 // "table_name (geom_col_name)"
    1698             :                 // The way we handle that might change in the future (e.g
    1699             :                 // could be a single layer with multiple geometry columns)
    1700             :                 std::string osLayerNameWithGeomColName =
    1701        6478 :                     pszGeomColName ? std::string(pszTableName) + " (" +
    1702             :                                          pszGeomColName + ')'
    1703        6416 :                                    : std::string(pszTableName);
    1704        3208 :                 if (cpl::contains(oExistingLayers, osLayerNameWithGeomColName))
    1705           1 :                     continue;
    1706        3207 :                 oExistingLayers.insert(osLayerNameWithGeomColName);
    1707             :                 const std::string osLayerName =
    1708             :                     bTableHasSeveralGeomColumns
    1709           3 :                         ? std::move(osLayerNameWithGeomColName)
    1710        6417 :                         : std::string(pszTableName);
    1711             :                 auto poLayer = std::make_unique<OGRGeoPackageTableLayer>(
    1712        6414 :                     this, osLayerName.c_str());
    1713        3207 :                 bool bHasZ = pszZ && atoi(pszZ) > 0;
    1714        3207 :                 bool bHasM = pszM && atoi(pszM) > 0;
    1715        3207 :                 if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
    1716             :                 {
    1717         644 :                     if (pszZ && atoi(pszZ) == 2)
    1718          14 :                         bHasZ = false;
    1719         644 :                     if (pszM && atoi(pszM) == 2)
    1720           6 :                         bHasM = false;
    1721             :                 }
    1722        3207 :                 poLayer->SetOpeningParameters(
    1723             :                     pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
    1724             :                     pszGeomColName, pszGeomType, bHasZ, bHasM);
    1725        3207 :                 m_apoLayers.push_back(std::move(poLayer));
    1726             :             }
    1727             :         }
    1728             :     }
    1729             : 
    1730        1315 :     bool bHasTileMatrixSet = false;
    1731        1315 :     if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
    1732             :     {
    1733         584 :         bHasTileMatrixSet = SQLGetInteger(hDB,
    1734             :                                           "SELECT 1 FROM sqlite_master WHERE "
    1735             :                                           "name = 'gpkg_tile_matrix_set' AND "
    1736             :                                           "type IN ('table', 'view')",
    1737             :                                           nullptr) == 1;
    1738             :     }
    1739        1315 :     if (bHasTileMatrixSet)
    1740             :     {
    1741             :         std::string osSQL =
    1742             :             "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
    1743             :             "c.min_x, c.min_y, c.max_x, c.max_y, "
    1744             :             "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
    1745             :             "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
    1746             :             "c.table_name = tms.table_name WHERE "
    1747         582 :             "data_type IN ('tiles', '2d-gridded-coverage')";
    1748         582 :         if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
    1749             :             osSubdatasetTableName =
    1750           2 :                 CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
    1751         582 :         if (!osSubdatasetTableName.empty())
    1752             :         {
    1753          16 :             char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
    1754             :                                            osSubdatasetTableName.c_str());
    1755          16 :             osSQL += pszTmp;
    1756          16 :             sqlite3_free(pszTmp);
    1757          16 :             SetPhysicalFilename(osFilename.c_str());
    1758             :         }
    1759         582 :         const int nTableLimit = GetOGRTableLimit();
    1760         582 :         if (nTableLimit > 0)
    1761             :         {
    1762         582 :             osSQL += " LIMIT ";
    1763         582 :             osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1764             :         }
    1765             : 
    1766         582 :         auto oResult = SQLQuery(hDB, osSQL.c_str());
    1767         582 :         if (!oResult)
    1768             :         {
    1769           0 :             return FALSE;
    1770             :         }
    1771             : 
    1772         582 :         if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
    1773             :         {
    1774           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1775             :                      "Cannot find table '%s' in GeoPackage dataset",
    1776             :                      osSubdatasetTableName.c_str());
    1777             :         }
    1778         581 :         else if (oResult->RowCount() == 1)
    1779             :         {
    1780         279 :             const char *pszTableName = oResult->GetValue(0, 0);
    1781         279 :             const char *pszIdentifier = oResult->GetValue(1, 0);
    1782         279 :             const char *pszDescription = oResult->GetValue(2, 0);
    1783         279 :             const char *pszSRSId = oResult->GetValue(3, 0);
    1784         279 :             const char *pszMinX = oResult->GetValue(4, 0);
    1785         279 :             const char *pszMinY = oResult->GetValue(5, 0);
    1786         279 :             const char *pszMaxX = oResult->GetValue(6, 0);
    1787         279 :             const char *pszMaxY = oResult->GetValue(7, 0);
    1788         279 :             const char *pszTMSMinX = oResult->GetValue(8, 0);
    1789         279 :             const char *pszTMSMinY = oResult->GetValue(9, 0);
    1790         279 :             const char *pszTMSMaxX = oResult->GetValue(10, 0);
    1791         279 :             const char *pszTMSMaxY = oResult->GetValue(11, 0);
    1792         279 :             const char *pszDataType = oResult->GetValue(12, 0);
    1793         279 :             if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
    1794             :                 pszTMSMaxY)
    1795             :             {
    1796         558 :                 bRet = OpenRaster(
    1797             :                     pszTableName, pszIdentifier, pszDescription,
    1798         279 :                     pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
    1799             :                     CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
    1800             :                     CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
    1801         279 :                     EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
    1802             :             }
    1803             :         }
    1804         302 :         else if (oResult->RowCount() >= 1)
    1805             :         {
    1806           5 :             bRet = TRUE;
    1807             : 
    1808           5 :             if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
    1809             :             {
    1810           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1811             :                          "File has more than %d raster tables. "
    1812             :                          "Limiting to first %d (can be overridden with "
    1813             :                          "OGR_TABLE_LIMIT config option)",
    1814             :                          nTableLimit, nTableLimit);
    1815           1 :                 oResult->LimitRowCount(nTableLimit);
    1816             :             }
    1817             : 
    1818           5 :             int nSDSCount = 0;
    1819        2013 :             for (int i = 0; i < oResult->RowCount(); i++)
    1820             :             {
    1821        2008 :                 const char *pszTableName = oResult->GetValue(0, i);
    1822        2008 :                 const char *pszIdentifier = oResult->GetValue(1, i);
    1823        2008 :                 if (pszTableName == nullptr)
    1824           0 :                     continue;
    1825             :                 m_aosSubDatasets.AddNameValue(
    1826             :                     CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
    1827        2008 :                     CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
    1828             :                 m_aosSubDatasets.AddNameValue(
    1829             :                     CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
    1830             :                     pszIdentifier
    1831        2008 :                         ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
    1832        4016 :                         : pszTableName);
    1833        2008 :                 nSDSCount++;
    1834             :             }
    1835             :         }
    1836             :     }
    1837             : 
    1838        1315 :     if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
    1839             :     {
    1840          33 :         if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
    1841             :         {
    1842          22 :             bRet = TRUE;
    1843             :         }
    1844             :         else
    1845             :         {
    1846          11 :             CPLDebug("GPKG",
    1847             :                      "This GeoPackage has no vector content and is opened "
    1848             :                      "in read-only mode. If you open it in update mode, "
    1849             :                      "opening will be successful.");
    1850             :         }
    1851             :     }
    1852             : 
    1853        1315 :     if (eAccess == GA_Update)
    1854             :     {
    1855         259 :         FixupWrongRTreeTrigger();
    1856         259 :         FixupWrongMedataReferenceColumnNameUpdate();
    1857             :     }
    1858             : 
    1859        1315 :     SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    1860             : 
    1861        1315 :     return bRet;
    1862             : }
    1863             : 
    1864             : /************************************************************************/
    1865             : /*                     DetectSpatialRefSysColumns()                     */
    1866             : /************************************************************************/
    1867             : 
    1868        1325 : void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
    1869             : {
    1870             :     // Detect definition_12_063 column
    1871             :     {
    1872        1325 :         sqlite3_stmt *hSQLStmt = nullptr;
    1873        1325 :         int rc = sqlite3_prepare_v2(
    1874             :             hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
    1875             :             &hSQLStmt, nullptr);
    1876        1325 :         if (rc == SQLITE_OK)
    1877             :         {
    1878          85 :             m_bHasDefinition12_063 = true;
    1879          85 :             sqlite3_finalize(hSQLStmt);
    1880             :         }
    1881             :     }
    1882             : 
    1883             :     // Detect epoch column
    1884        1325 :     if (m_bHasDefinition12_063)
    1885             :     {
    1886          85 :         sqlite3_stmt *hSQLStmt = nullptr;
    1887             :         int rc =
    1888          85 :             sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
    1889             :                                -1, &hSQLStmt, nullptr);
    1890          85 :         if (rc == SQLITE_OK)
    1891             :         {
    1892          76 :             m_bHasEpochColumn = true;
    1893          76 :             sqlite3_finalize(hSQLStmt);
    1894             :         }
    1895             :     }
    1896        1325 : }
    1897             : 
    1898             : /************************************************************************/
    1899             : /*                       FixupWrongRTreeTrigger()                       */
    1900             : /************************************************************************/
    1901             : 
    1902         259 : void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
    1903             : {
    1904             :     auto oResult = SQLQuery(
    1905             :         hDB,
    1906             :         "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
    1907         259 :         "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
    1908         259 :     if (oResult == nullptr)
    1909           0 :         return;
    1910         259 :     if (oResult->RowCount() > 0)
    1911             :     {
    1912           1 :         CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
    1913             :     }
    1914         261 :     for (int i = 0; i < oResult->RowCount(); i++)
    1915             :     {
    1916           2 :         const char *pszName = oResult->GetValue(0, i);
    1917           2 :         const char *pszSQL = oResult->GetValue(1, i);
    1918           2 :         const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
    1919           2 :         if (pszPtr1)
    1920             :         {
    1921           2 :             const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
    1922             :             // Skipping over geometry column name
    1923           4 :             while (*pszPtr == ' ')
    1924           2 :                 pszPtr++;
    1925           2 :             if (pszPtr[0] == '"' || pszPtr[0] == '\'')
    1926             :             {
    1927           1 :                 char chStringDelim = pszPtr[0];
    1928           1 :                 pszPtr++;
    1929           9 :                 while (*pszPtr != '\0' && *pszPtr != chStringDelim)
    1930             :                 {
    1931           8 :                     if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
    1932           0 :                         pszPtr += 2;
    1933             :                     else
    1934           8 :                         pszPtr += 1;
    1935             :                 }
    1936           1 :                 if (*pszPtr == chStringDelim)
    1937           1 :                     pszPtr++;
    1938             :             }
    1939             :             else
    1940             :             {
    1941           1 :                 pszPtr++;
    1942           8 :                 while (*pszPtr != ' ')
    1943           7 :                     pszPtr++;
    1944             :             }
    1945           2 :             if (*pszPtr == ' ')
    1946             :             {
    1947           2 :                 SQLCommand(hDB,
    1948           4 :                            ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
    1949             :                                .c_str());
    1950           4 :                 CPLString newSQL;
    1951           2 :                 newSQL.assign(pszSQL, pszPtr1 - pszSQL);
    1952           2 :                 newSQL += " AFTER UPDATE";
    1953           2 :                 newSQL += pszPtr;
    1954           2 :                 SQLCommand(hDB, newSQL);
    1955             :             }
    1956             :         }
    1957             :     }
    1958             : }
    1959             : 
    1960             : /************************************************************************/
    1961             : /*             FixupWrongMedataReferenceColumnNameUpdate()              */
    1962             : /************************************************************************/
    1963             : 
    1964         259 : void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
    1965             : {
    1966             :     // Fix wrong trigger that was generated by GDAL < 2.4.0
    1967             :     // See https://github.com/qgis/QGIS/issues/42768
    1968             :     auto oResult = SQLQuery(
    1969             :         hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
    1970             :              "NAME ='gpkg_metadata_reference_column_name_update' AND "
    1971         259 :              "sql LIKE '%column_nameIS%'");
    1972         259 :     if (oResult == nullptr)
    1973           0 :         return;
    1974         259 :     if (oResult->RowCount() == 1)
    1975             :     {
    1976           1 :         CPLDebug("GPKG", "Fixing incorrect trigger "
    1977             :                          "gpkg_metadata_reference_column_name_update");
    1978           1 :         const char *pszSQL = oResult->GetValue(0, 0);
    1979             :         std::string osNewSQL(
    1980           3 :             CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
    1981             : 
    1982           1 :         SQLCommand(hDB,
    1983             :                    "DROP TRIGGER gpkg_metadata_reference_column_name_update");
    1984           1 :         SQLCommand(hDB, osNewSQL.c_str());
    1985             :     }
    1986             : }
    1987             : 
    1988             : /************************************************************************/
    1989             : /*                      ClearCachedRelationships()                      */
    1990             : /************************************************************************/
    1991             : 
    1992          36 : void GDALGeoPackageDataset::ClearCachedRelationships()
    1993             : {
    1994          36 :     m_bHasPopulatedRelationships = false;
    1995          36 :     m_osMapRelationships.clear();
    1996          36 : }
    1997             : 
    1998             : /************************************************************************/
    1999             : /*                         LoadRelationships()                          */
    2000             : /************************************************************************/
    2001             : 
    2002          85 : void GDALGeoPackageDataset::LoadRelationships() const
    2003             : {
    2004          85 :     m_osMapRelationships.clear();
    2005             : 
    2006          85 :     std::vector<std::string> oExcludedTables;
    2007          85 :     if (HasGpkgextRelationsTable())
    2008             :     {
    2009          37 :         LoadRelationshipsUsingRelatedTablesExtension();
    2010             : 
    2011          89 :         for (const auto &oRelationship : m_osMapRelationships)
    2012             :         {
    2013             :             oExcludedTables.emplace_back(
    2014          52 :                 oRelationship.second->GetMappingTableName());
    2015             :         }
    2016             :     }
    2017             : 
    2018             :     // Also load relationships defined using foreign keys (i.e. one-to-many
    2019             :     // relationships). Here we must exclude any relationships defined from the
    2020             :     // related tables extension, we don't want them included twice.
    2021          85 :     LoadRelationshipsFromForeignKeys(oExcludedTables);
    2022          85 :     m_bHasPopulatedRelationships = true;
    2023          85 : }
    2024             : 
    2025             : /************************************************************************/
    2026             : /*            LoadRelationshipsUsingRelatedTablesExtension()            */
    2027             : /************************************************************************/
    2028             : 
    2029          37 : void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
    2030             : {
    2031          37 :     m_osMapRelationships.clear();
    2032             : 
    2033             :     auto oResultTable = SQLQuery(
    2034          37 :         hDB, "SELECT base_table_name, base_primary_column, "
    2035             :              "related_table_name, related_primary_column, relation_name, "
    2036          74 :              "mapping_table_name FROM gpkgext_relations");
    2037          37 :     if (oResultTable && oResultTable->RowCount() > 0)
    2038             :     {
    2039          86 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    2040             :         {
    2041          53 :             const char *pszBaseTableName = oResultTable->GetValue(0, i);
    2042          53 :             if (!pszBaseTableName)
    2043             :             {
    2044           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2045             :                          "Could not retrieve base_table_name from "
    2046             :                          "gpkgext_relations");
    2047           1 :                 continue;
    2048             :             }
    2049          53 :             const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
    2050          53 :             if (!pszBasePrimaryColumn)
    2051             :             {
    2052           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2053             :                          "Could not retrieve base_primary_column from "
    2054             :                          "gpkgext_relations");
    2055           0 :                 continue;
    2056             :             }
    2057          53 :             const char *pszRelatedTableName = oResultTable->GetValue(2, i);
    2058          53 :             if (!pszRelatedTableName)
    2059             :             {
    2060           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2061             :                          "Could not retrieve related_table_name from "
    2062             :                          "gpkgext_relations");
    2063           0 :                 continue;
    2064             :             }
    2065          53 :             const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
    2066          53 :             if (!pszRelatedPrimaryColumn)
    2067             :             {
    2068           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2069             :                          "Could not retrieve related_primary_column from "
    2070             :                          "gpkgext_relations");
    2071           0 :                 continue;
    2072             :             }
    2073          53 :             const char *pszRelationName = oResultTable->GetValue(4, i);
    2074          53 :             if (!pszRelationName)
    2075             :             {
    2076           0 :                 CPLError(
    2077             :                     CE_Warning, CPLE_AppDefined,
    2078             :                     "Could not retrieve relation_name from gpkgext_relations");
    2079           0 :                 continue;
    2080             :             }
    2081          53 :             const char *pszMappingTableName = oResultTable->GetValue(5, i);
    2082          53 :             if (!pszMappingTableName)
    2083             :             {
    2084           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2085             :                          "Could not retrieve mapping_table_name from "
    2086             :                          "gpkgext_relations");
    2087           0 :                 continue;
    2088             :             }
    2089             : 
    2090             :             // confirm that mapping table exists
    2091             :             char *pszSQL =
    2092          53 :                 sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
    2093             :                                 "name='%q' AND type IN ('table', 'view')",
    2094             :                                 pszMappingTableName);
    2095          53 :             const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
    2096          53 :             sqlite3_free(pszSQL);
    2097             : 
    2098          55 :             if (nMappingTableCount < 1 &&
    2099           2 :                 !const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
    2100           2 :                     pszMappingTableName))
    2101             :             {
    2102           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2103             :                          "Relationship mapping table %s does not exist",
    2104             :                          pszMappingTableName);
    2105           1 :                 continue;
    2106             :             }
    2107             : 
    2108             :             const std::string osRelationName = GenerateNameForRelationship(
    2109         104 :                 pszBaseTableName, pszRelatedTableName, pszRelationName);
    2110             : 
    2111         104 :             std::string osType{};
    2112             :             // defined requirement classes -- for these types the relation name
    2113             :             // will be specific string value from the related tables extension.
    2114             :             // In this case we need to construct a unique relationship name
    2115             :             // based on the related tables
    2116          52 :             if (EQUAL(pszRelationName, "media") ||
    2117          40 :                 EQUAL(pszRelationName, "simple_attributes") ||
    2118          40 :                 EQUAL(pszRelationName, "features") ||
    2119          18 :                 EQUAL(pszRelationName, "attributes") ||
    2120           2 :                 EQUAL(pszRelationName, "tiles"))
    2121             :             {
    2122          50 :                 osType = pszRelationName;
    2123             :             }
    2124             :             else
    2125             :             {
    2126             :                 // user defined types default to features
    2127           2 :                 osType = "features";
    2128             :             }
    2129             : 
    2130             :             auto poRelationship = std::make_unique<GDALRelationship>(
    2131             :                 osRelationName, pszBaseTableName, pszRelatedTableName,
    2132         104 :                 GRC_MANY_TO_MANY);
    2133             : 
    2134         104 :             poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
    2135         104 :             poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
    2136         104 :             poRelationship->SetLeftMappingTableFields({"base_id"});
    2137         104 :             poRelationship->SetRightMappingTableFields({"related_id"});
    2138          52 :             poRelationship->SetMappingTableName(pszMappingTableName);
    2139          52 :             poRelationship->SetRelatedTableType(osType);
    2140             : 
    2141          52 :             m_osMapRelationships[osRelationName] = std::move(poRelationship);
    2142             :         }
    2143             :     }
    2144          37 : }
    2145             : 
    2146             : /************************************************************************/
    2147             : /*                    GenerateNameForRelationship()                     */
    2148             : /************************************************************************/
    2149             : 
    2150          76 : std::string GDALGeoPackageDataset::GenerateNameForRelationship(
    2151             :     const char *pszBaseTableName, const char *pszRelatedTableName,
    2152             :     const char *pszType)
    2153             : {
    2154             :     // defined requirement classes -- for these types the relation name will be
    2155             :     // specific string value from the related tables extension. In this case we
    2156             :     // need to construct a unique relationship name based on the related tables
    2157          76 :     if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
    2158          53 :         EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
    2159           8 :         EQUAL(pszType, "tiles"))
    2160             :     {
    2161         136 :         std::ostringstream stream;
    2162             :         stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
    2163          68 :                << pszType;
    2164          68 :         return stream.str();
    2165             :     }
    2166             :     else
    2167             :     {
    2168             :         // user defined types default to features
    2169           8 :         return pszType;
    2170             :     }
    2171             : }
    2172             : 
    2173             : /************************************************************************/
    2174             : /*                        ValidateRelationship()                        */
    2175             : /************************************************************************/
    2176             : 
    2177          28 : bool GDALGeoPackageDataset::ValidateRelationship(
    2178             :     const GDALRelationship *poRelationship, std::string &failureReason)
    2179             : {
    2180             : 
    2181          28 :     if (poRelationship->GetCardinality() !=
    2182             :         GDALRelationshipCardinality::GRC_MANY_TO_MANY)
    2183             :     {
    2184           3 :         failureReason = "Only many to many relationships are supported";
    2185           3 :         return false;
    2186             :     }
    2187             : 
    2188          50 :     std::string osRelatedTableType = poRelationship->GetRelatedTableType();
    2189          65 :     if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
    2190          30 :         osRelatedTableType != "media" &&
    2191          20 :         osRelatedTableType != "simple_attributes" &&
    2192          55 :         osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
    2193             :     {
    2194             :         failureReason =
    2195           4 :             ("Related table type " + osRelatedTableType +
    2196             :              " is not a valid value for the GeoPackage specification. "
    2197             :              "Valid values are: features, media, simple_attributes, "
    2198             :              "attributes, tiles.")
    2199           2 :                 .c_str();
    2200           2 :         return false;
    2201             :     }
    2202             : 
    2203          23 :     const std::string &osLeftTableName = poRelationship->GetLeftTableName();
    2204          23 :     OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
    2205          23 :         GetLayerByName(osLeftTableName.c_str()));
    2206          23 :     if (!poLeftTable)
    2207             :     {
    2208           4 :         failureReason = ("Left table " + osLeftTableName +
    2209             :                          " is not an existing layer in the dataset")
    2210           2 :                             .c_str();
    2211           2 :         return false;
    2212             :     }
    2213          21 :     const std::string &osRightTableName = poRelationship->GetRightTableName();
    2214          21 :     OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
    2215          21 :         GetLayerByName(osRightTableName.c_str()));
    2216          21 :     if (!poRightTable)
    2217             :     {
    2218           4 :         failureReason = ("Right table " + osRightTableName +
    2219             :                          " is not an existing layer in the dataset")
    2220           2 :                             .c_str();
    2221           2 :         return false;
    2222             :     }
    2223             : 
    2224          19 :     const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
    2225          19 :     if (aosLeftTableFields.empty())
    2226             :     {
    2227           1 :         failureReason = "No left table fields were specified";
    2228           1 :         return false;
    2229             :     }
    2230          18 :     else if (aosLeftTableFields.size() > 1)
    2231             :     {
    2232             :         failureReason = "Only a single left table field is permitted for the "
    2233           1 :                         "GeoPackage specification";
    2234           1 :         return false;
    2235             :     }
    2236             :     else
    2237             :     {
    2238             :         // validate left field exists
    2239          34 :         if (poLeftTable->GetLayerDefn()->GetFieldIndex(
    2240          37 :                 aosLeftTableFields[0].c_str()) < 0 &&
    2241           3 :             !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
    2242             :         {
    2243           2 :             failureReason = ("Left table field " + aosLeftTableFields[0] +
    2244           2 :                              " does not exist in " + osLeftTableName)
    2245           1 :                                 .c_str();
    2246           1 :             return false;
    2247             :         }
    2248             :     }
    2249             : 
    2250          16 :     const auto &aosRightTableFields = poRelationship->GetRightTableFields();
    2251          16 :     if (aosRightTableFields.empty())
    2252             :     {
    2253           1 :         failureReason = "No right table fields were specified";
    2254           1 :         return false;
    2255             :     }
    2256          15 :     else if (aosRightTableFields.size() > 1)
    2257             :     {
    2258             :         failureReason = "Only a single right table field is permitted for the "
    2259           1 :                         "GeoPackage specification";
    2260           1 :         return false;
    2261             :     }
    2262             :     else
    2263             :     {
    2264             :         // validate right field exists
    2265          28 :         if (poRightTable->GetLayerDefn()->GetFieldIndex(
    2266          32 :                 aosRightTableFields[0].c_str()) < 0 &&
    2267           4 :             !EQUAL(poRightTable->GetFIDColumn(),
    2268             :                    aosRightTableFields[0].c_str()))
    2269             :         {
    2270           4 :             failureReason = ("Right table field " + aosRightTableFields[0] +
    2271           4 :                              " does not exist in " + osRightTableName)
    2272           2 :                                 .c_str();
    2273           2 :             return false;
    2274             :         }
    2275             :     }
    2276             : 
    2277          12 :     return true;
    2278             : }
    2279             : 
    2280             : /************************************************************************/
    2281             : /*                             InitRaster()                             */
    2282             : /************************************************************************/
    2283             : 
    2284         363 : bool GDALGeoPackageDataset::InitRaster(
    2285             :     GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
    2286             :     double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
    2287             :     const char *pszContentsMinY, const char *pszContentsMaxX,
    2288             :     const char *pszContentsMaxY, CSLConstList papszOpenOptionsIn,
    2289             :     const SQLResult &oResult, int nIdxInResult)
    2290             : {
    2291         363 :     m_osRasterTable = pszTableName;
    2292         363 :     m_dfTMSMinX = dfMinX;
    2293         363 :     m_dfTMSMaxY = dfMaxY;
    2294             : 
    2295             :     // Despite prior checking, the type might be Binary and
    2296             :     // SQLResultGetValue() not working properly on it
    2297         363 :     int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
    2298         363 :     if (nZoomLevel < 0 || nZoomLevel > 65536)
    2299             :     {
    2300           0 :         return false;
    2301             :     }
    2302         363 :     double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
    2303         363 :     double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
    2304         363 :     if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
    2305             :     {
    2306           0 :         return false;
    2307             :     }
    2308         363 :     int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
    2309         363 :     int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
    2310         363 :     if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
    2311             :         nTileHeight > 65536)
    2312             :     {
    2313           0 :         return false;
    2314             :     }
    2315             :     int nTileMatrixWidth = static_cast<int>(
    2316         726 :         std::min(static_cast<GIntBig>(INT_MAX),
    2317         363 :                  CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
    2318             :     int nTileMatrixHeight = static_cast<int>(
    2319         726 :         std::min(static_cast<GIntBig>(INT_MAX),
    2320         363 :                  CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
    2321         363 :     if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
    2322             :     {
    2323           0 :         return false;
    2324             :     }
    2325             : 
    2326             :     /* Use content bounds in priority over tile_matrix_set bounds */
    2327         363 :     double dfGDALMinX = dfMinX;
    2328         363 :     double dfGDALMinY = dfMinY;
    2329         363 :     double dfGDALMaxX = dfMaxX;
    2330         363 :     double dfGDALMaxY = dfMaxY;
    2331             :     pszContentsMinX =
    2332         363 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
    2333             :     pszContentsMinY =
    2334         363 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
    2335             :     pszContentsMaxX =
    2336         363 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
    2337             :     pszContentsMaxY =
    2338         363 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
    2339         363 :     if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
    2340         363 :         pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
    2341             :     {
    2342         725 :         if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
    2343         362 :             CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
    2344             :         {
    2345         362 :             dfGDALMinX = CPLAtof(pszContentsMinX);
    2346         362 :             dfGDALMinY = CPLAtof(pszContentsMinY);
    2347         362 :             dfGDALMaxX = CPLAtof(pszContentsMaxX);
    2348         362 :             dfGDALMaxY = CPLAtof(pszContentsMaxY);
    2349             :         }
    2350             :         else
    2351             :         {
    2352           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    2353             :                      "Illegal min_x/min_y/max_x/max_y values for %s in open "
    2354             :                      "options and/or gpkg_contents. Using bounds of "
    2355             :                      "gpkg_tile_matrix_set instead",
    2356             :                      pszTableName);
    2357             :         }
    2358             :     }
    2359         363 :     if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
    2360             :     {
    2361           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2362             :                  "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
    2363           0 :         return false;
    2364             :     }
    2365             : 
    2366         363 :     int nBandCount = 0;
    2367             :     const char *pszBAND_COUNT =
    2368         363 :         CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
    2369         363 :     if (poParentDS)
    2370             :     {
    2371          86 :         nBandCount = poParentDS->GetRasterCount();
    2372             :     }
    2373         277 :     else if (m_eDT != GDT_UInt8)
    2374             :     {
    2375          65 :         if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
    2376           0 :             !EQUAL(pszBAND_COUNT, "1"))
    2377             :         {
    2378           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    2379             :                      "BAND_COUNT ignored for non-Byte data");
    2380             :         }
    2381          65 :         nBandCount = 1;
    2382             :     }
    2383             :     else
    2384             :     {
    2385         212 :         if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
    2386             :         {
    2387          69 :             nBandCount = atoi(pszBAND_COUNT);
    2388          69 :             if (nBandCount == 1)
    2389           5 :                 GetMetadata("IMAGE_STRUCTURE");
    2390             :         }
    2391             :         else
    2392             :         {
    2393         143 :             GetMetadata("IMAGE_STRUCTURE");
    2394         143 :             nBandCount = m_nBandCountFromMetadata;
    2395         143 :             if (nBandCount == 1)
    2396          44 :                 m_eTF = GPKG_TF_PNG;
    2397             :         }
    2398         212 :         if (nBandCount == 1 && !m_osTFFromMetadata.empty())
    2399             :         {
    2400           2 :             m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
    2401             :         }
    2402         212 :         if (nBandCount <= 0 || nBandCount > 4)
    2403          85 :             nBandCount = 4;
    2404             :     }
    2405             : 
    2406         363 :     return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
    2407             :                       dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
    2408             :                       nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
    2409         363 :                       dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
    2410             : }
    2411             : 
    2412             : /************************************************************************/
    2413             : /*                     ComputeTileAndPixelShifts()                      */
    2414             : /************************************************************************/
    2415             : 
    2416         789 : bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
    2417             : {
    2418             :     int nTileWidth, nTileHeight;
    2419         789 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    2420             : 
    2421             :     // Compute shift between GDAL origin and TileMatrixSet origin
    2422         789 :     const double dfShiftXPixels = (m_gt[0] - m_dfTMSMinX) / m_gt[1];
    2423         789 :     if (!(dfShiftXPixels / nTileWidth >= INT_MIN &&
    2424         786 :           dfShiftXPixels / nTileWidth < INT_MAX))
    2425             :     {
    2426           3 :         return false;
    2427             :     }
    2428         786 :     const int64_t nShiftXPixels =
    2429         786 :         static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
    2430         786 :     m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
    2431         786 :     if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
    2432          11 :         m_nShiftXTiles--;
    2433         786 :     m_nShiftXPixelsMod =
    2434         786 :         (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
    2435             :         nTileWidth;
    2436             : 
    2437         786 :     const double dfShiftYPixels = (m_gt[3] - m_dfTMSMaxY) / m_gt[5];
    2438         786 :     if (!(dfShiftYPixels / nTileHeight >= INT_MIN &&
    2439         786 :           dfShiftYPixels / nTileHeight < INT_MAX))
    2440             :     {
    2441           1 :         return false;
    2442             :     }
    2443         785 :     const int64_t nShiftYPixels =
    2444         785 :         static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
    2445         785 :     m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
    2446         785 :     if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
    2447          11 :         m_nShiftYTiles--;
    2448         785 :     m_nShiftYPixelsMod =
    2449         785 :         (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
    2450             :         nTileHeight;
    2451         785 :     return true;
    2452             : }
    2453             : 
    2454             : /************************************************************************/
    2455             : /*                          AllocCachedTiles()                          */
    2456             : /************************************************************************/
    2457             : 
    2458         785 : bool GDALGeoPackageDataset::AllocCachedTiles()
    2459             : {
    2460             :     int nTileWidth, nTileHeight;
    2461         785 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    2462             : 
    2463             :     // We currently need 4 caches because of
    2464             :     // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
    2465         785 :     const int nCacheCount = 4;
    2466             :     /*
    2467             :             (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
    2468             :             (GetUpdate() && m_eDT == GDT_UInt8) ? 2 : 1;
    2469             :     */
    2470         785 :     m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
    2471             :         cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_UInt8 ? 4 : 1) *
    2472             :                           m_nDTSize),
    2473             :         nTileWidth, nTileHeight));
    2474         785 :     if (m_pabyCachedTiles == nullptr)
    2475             :     {
    2476           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
    2477             :                  nTileWidth, nTileHeight);
    2478           0 :         return false;
    2479             :     }
    2480             : 
    2481         785 :     return true;
    2482             : }
    2483             : 
    2484             : /************************************************************************/
    2485             : /*                             InitRaster()                             */
    2486             : /************************************************************************/
    2487             : 
    2488         602 : bool GDALGeoPackageDataset::InitRaster(
    2489             :     GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
    2490             :     int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
    2491             :     double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
    2492             :     int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
    2493             :     double dfGDALMaxX, double dfGDALMaxY)
    2494             : {
    2495         602 :     m_osRasterTable = pszTableName;
    2496         602 :     m_dfTMSMinX = dfTMSMinX;
    2497         602 :     m_dfTMSMaxY = dfTMSMaxY;
    2498         602 :     m_nZoomLevel = nZoomLevel;
    2499         602 :     m_nTileMatrixWidth = nTileMatrixWidth;
    2500         602 :     m_nTileMatrixHeight = nTileMatrixHeight;
    2501             : 
    2502         602 :     m_bGeoTransformValid = true;
    2503         602 :     m_gt[0] = dfGDALMinX;
    2504         602 :     m_gt[1] = dfPixelXSize;
    2505         602 :     m_gt[3] = dfGDALMaxY;
    2506         602 :     m_gt[5] = -dfPixelYSize;
    2507         602 :     double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
    2508         602 :     double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
    2509         602 :     if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
    2510             :     {
    2511           0 :         CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
    2512             :                  dfRasterXSize, dfRasterYSize);
    2513           0 :         return false;
    2514             :     }
    2515         602 :     nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
    2516         602 :     nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
    2517             : 
    2518         602 :     if (poParentDS)
    2519             :     {
    2520         325 :         m_poParentDS = poParentDS;
    2521         325 :         eAccess = poParentDS->eAccess;
    2522         325 :         hDB = poParentDS->hDB;
    2523         325 :         m_eTF = poParentDS->m_eTF;
    2524         325 :         m_eDT = poParentDS->m_eDT;
    2525         325 :         m_nDTSize = poParentDS->m_nDTSize;
    2526         325 :         m_dfScale = poParentDS->m_dfScale;
    2527         325 :         m_dfOffset = poParentDS->m_dfOffset;
    2528         325 :         m_dfPrecision = poParentDS->m_dfPrecision;
    2529         325 :         m_usGPKGNull = poParentDS->m_usGPKGNull;
    2530         325 :         m_nQuality = poParentDS->m_nQuality;
    2531         325 :         m_nZLevel = poParentDS->m_nZLevel;
    2532         325 :         m_bDither = poParentDS->m_bDither;
    2533             :         /*m_nSRID = poParentDS->m_nSRID;*/
    2534         325 :         m_osWHERE = poParentDS->m_osWHERE;
    2535         325 :         SetDescription(CPLSPrintf("%s - zoom_level=%d",
    2536         325 :                                   poParentDS->GetDescription(), m_nZoomLevel));
    2537             :     }
    2538             : 
    2539        2101 :     for (int i = 1; i <= nBandCount; i++)
    2540             :     {
    2541             :         auto poNewBand = std::make_unique<GDALGeoPackageRasterBand>(
    2542        1499 :             this, nTileWidth, nTileHeight);
    2543        1499 :         if (poParentDS)
    2544             :         {
    2545         761 :             int bHasNoData = FALSE;
    2546             :             double dfNoDataValue =
    2547         761 :                 poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    2548         761 :             if (bHasNoData)
    2549          24 :                 poNewBand->SetNoDataValueInternal(dfNoDataValue);
    2550             :         }
    2551             : 
    2552        1499 :         if (nBandCount == 1 && m_poCTFromMetadata)
    2553             :         {
    2554           3 :             poNewBand->AssignColorTable(m_poCTFromMetadata.get());
    2555             :         }
    2556        1499 :         if (!m_osNodataValueFromMetadata.empty())
    2557             :         {
    2558           8 :             poNewBand->SetNoDataValueInternal(
    2559             :                 CPLAtof(m_osNodataValueFromMetadata.c_str()));
    2560             :         }
    2561             : 
    2562        1499 :         SetBand(i, std::move(poNewBand));
    2563             :     }
    2564             : 
    2565         602 :     if (!ComputeTileAndPixelShifts())
    2566             :     {
    2567           3 :         CPLError(CE_Failure, CPLE_AppDefined,
    2568             :                  "Overflow occurred in ComputeTileAndPixelShifts()");
    2569           3 :         return false;
    2570             :     }
    2571             : 
    2572         599 :     GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    2573         599 :     GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
    2574             :                                     CPLSPrintf("%d", m_nZoomLevel));
    2575             : 
    2576         599 :     return AllocCachedTiles();
    2577             : }
    2578             : 
    2579             : /************************************************************************/
    2580             : /*                    GDALGPKGMBTilesGetTileFormat()                    */
    2581             : /************************************************************************/
    2582             : 
    2583          80 : GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
    2584             : {
    2585          80 :     GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
    2586          80 :     if (pszTF)
    2587             :     {
    2588          80 :         if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
    2589           1 :             eTF = GPKG_TF_PNG_JPEG;
    2590          79 :         else if (EQUAL(pszTF, "PNG"))
    2591          46 :             eTF = GPKG_TF_PNG;
    2592          33 :         else if (EQUAL(pszTF, "PNG8"))
    2593           6 :             eTF = GPKG_TF_PNG8;
    2594          27 :         else if (EQUAL(pszTF, "JPEG"))
    2595          14 :             eTF = GPKG_TF_JPEG;
    2596          13 :         else if (EQUAL(pszTF, "WEBP"))
    2597          13 :             eTF = GPKG_TF_WEBP;
    2598             :         else
    2599             :         {
    2600           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    2601             :                      "Unsuppoted value for TILE_FORMAT: %s", pszTF);
    2602             :         }
    2603             :     }
    2604          80 :     return eTF;
    2605             : }
    2606             : 
    2607          28 : const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
    2608             : {
    2609          28 :     switch (eTF)
    2610             :     {
    2611          26 :         case GPKG_TF_PNG:
    2612             :         case GPKG_TF_PNG8:
    2613          26 :             return "png";
    2614           1 :         case GPKG_TF_JPEG:
    2615           1 :             return "jpg";
    2616           1 :         case GPKG_TF_WEBP:
    2617           1 :             return "webp";
    2618           0 :         default:
    2619           0 :             break;
    2620             :     }
    2621           0 :     CPLError(CE_Failure, CPLE_NotSupported,
    2622             :              "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
    2623           0 :     return nullptr;
    2624             : }
    2625             : 
    2626             : /************************************************************************/
    2627             : /*                             OpenRaster()                             */
    2628             : /************************************************************************/
    2629             : 
    2630         279 : bool GDALGeoPackageDataset::OpenRaster(
    2631             :     const char *pszTableName, const char *pszIdentifier,
    2632             :     const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
    2633             :     double dfMaxX, double dfMaxY, const char *pszContentsMinX,
    2634             :     const char *pszContentsMinY, const char *pszContentsMaxX,
    2635             :     const char *pszContentsMaxY, bool bIsTiles, CSLConstList papszOpenOptionsIn)
    2636             : {
    2637         279 :     if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
    2638           0 :         return false;
    2639             : 
    2640             :     // Config option just for debug, and for example force set to NaN
    2641             :     // which is not supported
    2642         558 :     CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
    2643         558 :     CPLString osUom;
    2644         558 :     CPLString osFieldName;
    2645         558 :     CPLString osGridCellEncoding;
    2646         279 :     if (!bIsTiles)
    2647             :     {
    2648          65 :         char *pszSQL = sqlite3_mprintf(
    2649             :             "SELECT datatype, scale, offset, data_null, precision FROM "
    2650             :             "gpkg_2d_gridded_coverage_ancillary "
    2651             :             "WHERE tile_matrix_set_name = '%q' "
    2652             :             "AND datatype IN ('integer', 'float')"
    2653             :             "AND (scale > 0 OR scale IS NULL)",
    2654             :             pszTableName);
    2655          65 :         auto oResult = SQLQuery(hDB, pszSQL);
    2656          65 :         sqlite3_free(pszSQL);
    2657          65 :         if (!oResult || oResult->RowCount() == 0)
    2658             :         {
    2659           0 :             return false;
    2660             :         }
    2661          65 :         const char *pszDataType = oResult->GetValue(0, 0);
    2662          65 :         const char *pszScale = oResult->GetValue(1, 0);
    2663          65 :         const char *pszOffset = oResult->GetValue(2, 0);
    2664          65 :         const char *pszDataNull = oResult->GetValue(3, 0);
    2665          65 :         const char *pszPrecision = oResult->GetValue(4, 0);
    2666          65 :         if (pszDataNull)
    2667          23 :             osDataNull = pszDataNull;
    2668          65 :         if (EQUAL(pszDataType, "float"))
    2669             :         {
    2670           6 :             SetDataType(GDT_Float32);
    2671           6 :             m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
    2672             :         }
    2673             :         else
    2674             :         {
    2675          59 :             SetDataType(GDT_Float32);
    2676          59 :             m_eTF = GPKG_TF_PNG_16BIT;
    2677          59 :             const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
    2678          59 :             const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
    2679          59 :             if (dfScale == 1.0)
    2680             :             {
    2681          59 :                 if (dfOffset == 0.0)
    2682             :                 {
    2683          24 :                     SetDataType(GDT_UInt16);
    2684             :                 }
    2685          35 :                 else if (dfOffset == -32768.0)
    2686             :                 {
    2687          35 :                     SetDataType(GDT_Int16);
    2688             :                 }
    2689             :                 // coverity[tainted_data]
    2690           0 :                 else if (dfOffset == -32767.0 && !osDataNull.empty() &&
    2691           0 :                          CPLAtof(osDataNull) == 65535.0)
    2692             :                 // Given that we will map the nodata value to -32768
    2693             :                 {
    2694           0 :                     SetDataType(GDT_Int16);
    2695             :                 }
    2696             :             }
    2697             : 
    2698             :             // Check that the tile offset and scales are compatible of a
    2699             :             // final integer result.
    2700          59 :             if (m_eDT != GDT_Float32)
    2701             :             {
    2702             :                 // coverity[tainted_data]
    2703          59 :                 if (dfScale == 1.0 && dfOffset == -32768.0 &&
    2704         118 :                     !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
    2705             :                 {
    2706             :                     // Given that we will map the nodata value to -32768
    2707           9 :                     pszSQL = sqlite3_mprintf(
    2708             :                         "SELECT 1 FROM "
    2709             :                         "gpkg_2d_gridded_tile_ancillary WHERE "
    2710             :                         "tpudt_name = '%q' "
    2711             :                         "AND NOT ((offset = 0.0 or offset = 1.0) "
    2712             :                         "AND scale = 1.0) "
    2713             :                         "LIMIT 1",
    2714             :                         pszTableName);
    2715             :                 }
    2716             :                 else
    2717             :                 {
    2718          50 :                     pszSQL = sqlite3_mprintf(
    2719             :                         "SELECT 1 FROM "
    2720             :                         "gpkg_2d_gridded_tile_ancillary WHERE "
    2721             :                         "tpudt_name = '%q' "
    2722             :                         "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
    2723             :                         pszTableName);
    2724             :                 }
    2725          59 :                 sqlite3_stmt *hSQLStmt = nullptr;
    2726             :                 int rc =
    2727          59 :                     SQLPrepareWithError(hDB, pszSQL, -1, &hSQLStmt, nullptr);
    2728             : 
    2729          59 :                 if (rc == SQLITE_OK)
    2730             :                 {
    2731          59 :                     if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
    2732             :                     {
    2733           8 :                         SetDataType(GDT_Float32);
    2734             :                     }
    2735          59 :                     sqlite3_finalize(hSQLStmt);
    2736             :                 }
    2737          59 :                 sqlite3_free(pszSQL);
    2738             :             }
    2739             : 
    2740          59 :             SetGlobalOffsetScale(dfOffset, dfScale);
    2741             :         }
    2742          65 :         if (pszPrecision)
    2743          65 :             m_dfPrecision = CPLAtof(pszPrecision);
    2744             : 
    2745             :         // Request those columns in a separate query, so as to keep
    2746             :         // compatibility with pre OGC 17-066r1 databases
    2747             :         pszSQL =
    2748          65 :             sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
    2749             :                             "gpkg_2d_gridded_coverage_ancillary "
    2750             :                             "WHERE tile_matrix_set_name = '%q'",
    2751             :                             pszTableName);
    2752          65 :         CPLPushErrorHandler(CPLQuietErrorHandler);
    2753          65 :         oResult = SQLQuery(hDB, pszSQL);
    2754          65 :         CPLPopErrorHandler();
    2755          65 :         sqlite3_free(pszSQL);
    2756          65 :         if (oResult && oResult->RowCount() == 1)
    2757             :         {
    2758          64 :             const char *pszUom = oResult->GetValue(0, 0);
    2759          64 :             if (pszUom)
    2760           2 :                 osUom = pszUom;
    2761          64 :             const char *pszFieldName = oResult->GetValue(1, 0);
    2762          64 :             if (pszFieldName)
    2763          64 :                 osFieldName = pszFieldName;
    2764          64 :             const char *pszGridCellEncoding = oResult->GetValue(2, 0);
    2765          64 :             if (pszGridCellEncoding)
    2766          64 :                 osGridCellEncoding = pszGridCellEncoding;
    2767             :         }
    2768             :     }
    2769             : 
    2770         279 :     m_bRecordInsertedInGPKGContent = true;
    2771         279 :     m_nSRID = nSRSId;
    2772             : 
    2773         557 :     if (auto poSRS = GetSpatialRef(nSRSId))
    2774             :     {
    2775         278 :         m_oSRS = *(poSRS.get());
    2776             :     }
    2777             : 
    2778             :     /* Various sanity checks added in the SELECT */
    2779         279 :     char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
    2780         558 :     CPLString osQuotedTableName(pszQuotedTableName);
    2781         279 :     sqlite3_free(pszQuotedTableName);
    2782         279 :     char *pszSQL = sqlite3_mprintf(
    2783             :         "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
    2784             :         "tile_height, matrix_width, matrix_height "
    2785             :         "FROM gpkg_tile_matrix tm "
    2786             :         "WHERE table_name = %s "
    2787             :         // INT_MAX would be the theoretical maximum value to avoid
    2788             :         // overflows, but that's already a insane value.
    2789             :         "AND zoom_level >= 0 AND zoom_level <= 65536 "
    2790             :         "AND pixel_x_size > 0 AND pixel_y_size > 0 "
    2791             :         "AND tile_width >= 1 AND tile_width <= 65536 "
    2792             :         "AND tile_height >= 1 AND tile_height <= 65536 "
    2793             :         "AND matrix_width >= 1 AND matrix_height >= 1",
    2794             :         osQuotedTableName.c_str());
    2795         558 :     CPLString osSQL(pszSQL);
    2796             :     const char *pszZoomLevel =
    2797         279 :         CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
    2798         279 :     if (pszZoomLevel)
    2799             :     {
    2800           5 :         if (GetUpdate())
    2801           1 :             osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
    2802             :         else
    2803             :         {
    2804             :             osSQL += CPLSPrintf(
    2805             :                 " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
    2806             :                 "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
    2807             :                 atoi(pszZoomLevel), atoi(pszZoomLevel),
    2808           4 :                 osQuotedTableName.c_str());
    2809             :         }
    2810             :     }
    2811             :     // In read-only mode, only lists non empty zoom levels
    2812         274 :     else if (!GetUpdate())
    2813             :     {
    2814             :         osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
    2815             :                             "tm.zoom_level LIMIT 1)",
    2816         220 :                             osQuotedTableName.c_str());
    2817             :     }
    2818             :     else  // if( pszZoomLevel == nullptr )
    2819             :     {
    2820             :         osSQL +=
    2821             :             CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
    2822          54 :                        osQuotedTableName.c_str());
    2823             :     }
    2824         279 :     osSQL += " ORDER BY zoom_level DESC";
    2825             :     // To avoid denial of service.
    2826         279 :     osSQL += " LIMIT 100";
    2827             : 
    2828         558 :     auto oResult = SQLQuery(hDB, osSQL.c_str());
    2829         279 :     if (!oResult || oResult->RowCount() == 0)
    2830             :     {
    2831         114 :         if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
    2832         114 :             pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
    2833             :             pszContentsMaxY != nullptr)
    2834             :         {
    2835          56 :             osSQL = pszSQL;
    2836          56 :             osSQL += " ORDER BY zoom_level DESC";
    2837          56 :             if (!GetUpdate())
    2838          30 :                 osSQL += " LIMIT 1";
    2839          56 :             oResult = SQLQuery(hDB, osSQL.c_str());
    2840             :         }
    2841          57 :         if (!oResult || oResult->RowCount() == 0)
    2842             :         {
    2843           1 :             if (oResult && pszZoomLevel != nullptr)
    2844             :             {
    2845           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2846             :                          "ZOOM_LEVEL is probably not valid w.r.t tile "
    2847             :                          "table content");
    2848             :             }
    2849           1 :             sqlite3_free(pszSQL);
    2850           1 :             return false;
    2851             :         }
    2852             :     }
    2853         278 :     sqlite3_free(pszSQL);
    2854             : 
    2855             :     // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
    2856             :     // actually exist.
    2857             : 
    2858             :     // CAUTION: Do not move those variables inside inner scope !
    2859         556 :     CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
    2860             : 
    2861         278 :     if (CPLTestBool(
    2862             :             CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
    2863             :     {
    2864          13 :         pszSQL = sqlite3_mprintf(
    2865             :             "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
    2866             :             "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
    2867             :             pszTableName, atoi(oResult->GetValue(0, 0)));
    2868          13 :         auto oResult2 = SQLQuery(hDB, pszSQL);
    2869          13 :         sqlite3_free(pszSQL);
    2870          26 :         if (!oResult2 || oResult2->RowCount() == 0 ||
    2871             :             // Can happen if table is empty
    2872          38 :             oResult2->GetValue(0, 0) == nullptr ||
    2873             :             // Can happen if table has no NOT NULL constraint on tile_row
    2874             :             // and that all tile_row are NULL
    2875          12 :             oResult2->GetValue(1, 0) == nullptr)
    2876             :         {
    2877           1 :             return false;
    2878             :         }
    2879          12 :         const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
    2880          12 :         const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
    2881          12 :         const int nTileWidth = atoi(oResult->GetValue(3, 0));
    2882          12 :         const int nTileHeight = atoi(oResult->GetValue(4, 0));
    2883             :         osContentsMinX =
    2884          24 :             CPLSPrintf("%.17g", dfMinX + dfPixelXSize * nTileWidth *
    2885          12 :                                              atoi(oResult2->GetValue(0, 0)));
    2886             :         osContentsMaxY =
    2887          24 :             CPLSPrintf("%.17g", dfMaxY - dfPixelYSize * nTileHeight *
    2888          12 :                                              atoi(oResult2->GetValue(1, 0)));
    2889             :         osContentsMaxX = CPLSPrintf(
    2890          24 :             "%.17g", dfMinX + dfPixelXSize * nTileWidth *
    2891          12 :                                   (1 + atoi(oResult2->GetValue(2, 0))));
    2892             :         osContentsMinY = CPLSPrintf(
    2893          24 :             "%.17g", dfMaxY - dfPixelYSize * nTileHeight *
    2894          12 :                                   (1 + atoi(oResult2->GetValue(3, 0))));
    2895          12 :         pszContentsMinX = osContentsMinX.c_str();
    2896          12 :         pszContentsMinY = osContentsMinY.c_str();
    2897          12 :         pszContentsMaxX = osContentsMaxX.c_str();
    2898          12 :         pszContentsMaxY = osContentsMaxY.c_str();
    2899             :     }
    2900             : 
    2901         277 :     if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
    2902             :                     pszContentsMinX, pszContentsMinY, pszContentsMaxX,
    2903         277 :                     pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
    2904             :     {
    2905           3 :         return false;
    2906             :     }
    2907             : 
    2908         274 :     auto poBand = cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
    2909         274 :     if (!osDataNull.empty())
    2910             :     {
    2911          23 :         double dfGPKGNoDataValue = CPLAtof(osDataNull);
    2912          23 :         if (m_eTF == GPKG_TF_PNG_16BIT)
    2913             :         {
    2914          21 :             if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
    2915          21 :                 static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
    2916             :             {
    2917           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2918             :                          "data_null = %.17g is invalid for integer data_type",
    2919             :                          dfGPKGNoDataValue);
    2920             :             }
    2921             :             else
    2922             :             {
    2923          21 :                 m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
    2924          21 :                 if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
    2925           9 :                     dfGPKGNoDataValue = -32768.0;
    2926          12 :                 else if (m_eDT == GDT_Float32)
    2927             :                 {
    2928             :                     // Pick a value that is unlikely to be hit with offset &
    2929             :                     // scale
    2930           4 :                     dfGPKGNoDataValue = -std::numeric_limits<float>::max();
    2931             :                 }
    2932          21 :                 poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
    2933             :             }
    2934             :         }
    2935             :         else
    2936             :         {
    2937           2 :             poBand->SetNoDataValueInternal(
    2938           2 :                 static_cast<float>(dfGPKGNoDataValue));
    2939             :         }
    2940             :     }
    2941         274 :     if (!osUom.empty())
    2942             :     {
    2943           2 :         poBand->SetUnitTypeInternal(osUom);
    2944             :     }
    2945         274 :     if (!osFieldName.empty())
    2946             :     {
    2947          64 :         GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
    2948             :     }
    2949         274 :     if (!osGridCellEncoding.empty())
    2950             :     {
    2951          64 :         if (osGridCellEncoding == "grid-value-is-center")
    2952             :         {
    2953          15 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    2954             :                                             GDALMD_AOP_POINT);
    2955             :         }
    2956          49 :         else if (osGridCellEncoding == "grid-value-is-area")
    2957             :         {
    2958          45 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    2959             :                                             GDALMD_AOP_AREA);
    2960             :         }
    2961             :         else
    2962             :         {
    2963           4 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    2964             :                                             GDALMD_AOP_POINT);
    2965           4 :             GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
    2966             :                 "GRID_CELL_ENCODING", osGridCellEncoding);
    2967             :         }
    2968             :     }
    2969             : 
    2970         274 :     CheckUnknownExtensions(true);
    2971             : 
    2972             :     // Do this after CheckUnknownExtensions() so that m_eTF is set to
    2973             :     // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
    2974         274 :     const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
    2975         274 :     if (pszTF)
    2976             :     {
    2977           4 :         if (!GetUpdate())
    2978             :         {
    2979           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    2980             :                      "TILE_FORMAT open option ignored in read-only mode");
    2981             :         }
    2982           4 :         else if (m_eTF == GPKG_TF_PNG_16BIT ||
    2983           4 :                  m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
    2984             :         {
    2985           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    2986             :                      "TILE_FORMAT open option ignored on gridded coverages");
    2987             :         }
    2988             :         else
    2989             :         {
    2990           4 :             GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
    2991           4 :             if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
    2992             :             {
    2993           1 :                 if (!RegisterWebPExtension())
    2994           0 :                     return false;
    2995             :             }
    2996           4 :             m_eTF = eTF;
    2997             :         }
    2998             :     }
    2999             : 
    3000         274 :     ParseCompressionOptions(papszOpenOptionsIn);
    3001             : 
    3002         274 :     m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
    3003             : 
    3004             :     // Set metadata
    3005         274 :     if (pszIdentifier && pszIdentifier[0])
    3006         274 :         GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
    3007         274 :     if (pszDescription && pszDescription[0])
    3008          21 :         GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
    3009             : 
    3010             :     // Add overviews
    3011         359 :     for (int i = 1; i < oResult->RowCount(); i++)
    3012             :     {
    3013          86 :         auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
    3014          86 :         poOvrDS->ShareLockWithParentDataset(this);
    3015         172 :         if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
    3016             :                                  dfMaxY, pszContentsMinX, pszContentsMinY,
    3017             :                                  pszContentsMaxX, pszContentsMaxY,
    3018          86 :                                  papszOpenOptionsIn, *oResult, i))
    3019             :         {
    3020           0 :             break;
    3021             :         }
    3022             : 
    3023             :         int nTileWidth, nTileHeight;
    3024          86 :         poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3025             :         const bool bStop =
    3026          87 :             (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
    3027           1 :              poOvrDS->GetRasterYSize() < nTileHeight);
    3028             : 
    3029          86 :         m_apoOverviewDS.push_back(std::move(poOvrDS));
    3030             : 
    3031          86 :         if (bStop)
    3032             :         {
    3033           1 :             break;
    3034             :         }
    3035             :     }
    3036             : 
    3037         274 :     return true;
    3038             : }
    3039             : 
    3040             : /************************************************************************/
    3041             : /*                           GetSpatialRef()                            */
    3042             : /************************************************************************/
    3043             : 
    3044          17 : const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
    3045             : {
    3046          17 :     if (GetLayerCount())
    3047           1 :         return GDALDataset::GetSpatialRef();
    3048          16 :     return GetSpatialRefRasterOnly();
    3049             : }
    3050             : 
    3051             : /************************************************************************/
    3052             : /*                      GetSpatialRefRasterOnly()                       */
    3053             : /************************************************************************/
    3054             : 
    3055             : const OGRSpatialReference *
    3056          17 : GDALGeoPackageDataset::GetSpatialRefRasterOnly() const
    3057             : 
    3058             : {
    3059          17 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3060             : }
    3061             : 
    3062             : /************************************************************************/
    3063             : /*                           SetSpatialRef()                            */
    3064             : /************************************************************************/
    3065             : 
    3066         152 : CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    3067             : {
    3068         152 :     if (nBands == 0)
    3069             :     {
    3070           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3071             :                  "SetProjection() not supported on a dataset with 0 band");
    3072           1 :         return CE_Failure;
    3073             :     }
    3074         151 :     if (eAccess != GA_Update)
    3075             :     {
    3076           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3077             :                  "SetProjection() not supported on read-only dataset");
    3078           1 :         return CE_Failure;
    3079             :     }
    3080             : 
    3081         150 :     const int nSRID = GetSrsId(poSRS);
    3082         300 :     const auto poTS = GetTilingScheme(m_osTilingScheme);
    3083         150 :     if (poTS && nSRID != poTS->nEPSGCode)
    3084             :     {
    3085           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    3086             :                  "Projection should be EPSG:%d for %s tiling scheme",
    3087           1 :                  poTS->nEPSGCode, m_osTilingScheme.c_str());
    3088           1 :         return CE_Failure;
    3089             :     }
    3090             : 
    3091         149 :     m_nSRID = nSRID;
    3092         149 :     m_oSRS.Clear();
    3093         149 :     if (poSRS)
    3094         148 :         m_oSRS = *poSRS;
    3095             : 
    3096         149 :     if (m_bRecordInsertedInGPKGContent)
    3097             :     {
    3098         121 :         char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
    3099             :                                        "WHERE lower(table_name) = lower('%q')",
    3100             :                                        m_nSRID, m_osRasterTable.c_str());
    3101         121 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
    3102         121 :         sqlite3_free(pszSQL);
    3103         121 :         if (eErr != OGRERR_NONE)
    3104           0 :             return CE_Failure;
    3105             : 
    3106         121 :         pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
    3107             :                                  "WHERE lower(table_name) = lower('%q')",
    3108             :                                  m_nSRID, m_osRasterTable.c_str());
    3109         121 :         eErr = SQLCommand(hDB, pszSQL);
    3110         121 :         sqlite3_free(pszSQL);
    3111         121 :         if (eErr != OGRERR_NONE)
    3112           0 :             return CE_Failure;
    3113             :     }
    3114             : 
    3115         149 :     return CE_None;
    3116             : }
    3117             : 
    3118             : /************************************************************************/
    3119             : /*                          GetGeoTransform()                           */
    3120             : /************************************************************************/
    3121             : 
    3122          33 : CPLErr GDALGeoPackageDataset::GetGeoTransform(GDALGeoTransform &gt) const
    3123             : {
    3124          33 :     gt = m_gt;
    3125          33 :     if (!m_bGeoTransformValid)
    3126           2 :         return CE_Failure;
    3127             :     else
    3128          31 :         return CE_None;
    3129             : }
    3130             : 
    3131             : /************************************************************************/
    3132             : /*                          SetGeoTransform()                           */
    3133             : /************************************************************************/
    3134             : 
    3135         192 : CPLErr GDALGeoPackageDataset::SetGeoTransform(const GDALGeoTransform &gt)
    3136             : {
    3137         192 :     if (nBands == 0)
    3138             :     {
    3139           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    3140             :                  "SetGeoTransform() not supported on a dataset with 0 band");
    3141           2 :         return CE_Failure;
    3142             :     }
    3143         190 :     if (eAccess != GA_Update)
    3144             :     {
    3145           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3146             :                  "SetGeoTransform() not supported on read-only dataset");
    3147           1 :         return CE_Failure;
    3148             :     }
    3149         189 :     if (m_bGeoTransformValid)
    3150             :     {
    3151           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3152             :                  "Cannot modify geotransform once set");
    3153           1 :         return CE_Failure;
    3154             :     }
    3155         188 :     if (gt[2] != 0.0 || gt[4] != 0 || gt[5] > 0.0)
    3156             :     {
    3157           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3158             :                  "Only north-up non rotated geotransform supported");
    3159           0 :         return CE_Failure;
    3160             :     }
    3161             : 
    3162         188 :     if (m_nZoomLevel < 0)
    3163             :     {
    3164         187 :         const auto poTS = GetTilingScheme(m_osTilingScheme);
    3165         187 :         if (poTS)
    3166             :         {
    3167          20 :             double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
    3168          20 :             double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
    3169         199 :             for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
    3170         179 :                  m_nZoomLevel++)
    3171             :             {
    3172         198 :                 double dfExpectedPixelXSize =
    3173         198 :                     dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
    3174         198 :                 double dfExpectedPixelYSize =
    3175         198 :                     dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
    3176         198 :                 if (fabs(gt[1] - dfExpectedPixelXSize) <
    3177         217 :                         1e-8 * dfExpectedPixelXSize &&
    3178          19 :                     fabs(fabs(gt[5]) - dfExpectedPixelYSize) <
    3179          19 :                         1e-8 * dfExpectedPixelYSize)
    3180             :                 {
    3181          19 :                     break;
    3182             :                 }
    3183             :             }
    3184          20 :             if (m_nZoomLevel == MAX_ZOOM_LEVEL)
    3185             :             {
    3186           1 :                 m_nZoomLevel = -1;
    3187           1 :                 CPLError(
    3188             :                     CE_Failure, CPLE_NotSupported,
    3189             :                     "Could not find an appropriate zoom level of %s tiling "
    3190             :                     "scheme that matches raster pixel size",
    3191             :                     m_osTilingScheme.c_str());
    3192           1 :                 return CE_Failure;
    3193             :             }
    3194             :         }
    3195             :     }
    3196             : 
    3197         187 :     m_gt = gt;
    3198         187 :     m_bGeoTransformValid = true;
    3199             : 
    3200         187 :     return FinalizeRasterRegistration();
    3201             : }
    3202             : 
    3203             : /************************************************************************/
    3204             : /*                     FinalizeRasterRegistration()                     */
    3205             : /************************************************************************/
    3206             : 
    3207         187 : CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
    3208             : {
    3209             :     OGRErr eErr;
    3210             : 
    3211         187 :     m_dfTMSMinX = m_gt[0];
    3212         187 :     m_dfTMSMaxY = m_gt[3];
    3213             : 
    3214             :     int nTileWidth, nTileHeight;
    3215         187 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3216             : 
    3217         187 :     if (m_nZoomLevel < 0)
    3218             :     {
    3219         167 :         m_nZoomLevel = 0;
    3220         241 :         while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
    3221         167 :                (nRasterYSize >> m_nZoomLevel) > nTileHeight)
    3222          74 :             m_nZoomLevel++;
    3223             :     }
    3224             : 
    3225         187 :     double dfPixelXSizeZoomLevel0 = m_gt[1] * (1 << m_nZoomLevel);
    3226         187 :     double dfPixelYSizeZoomLevel0 = fabs(m_gt[5]) * (1 << m_nZoomLevel);
    3227             :     int nTileXCountZoomLevel0 =
    3228         187 :         std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
    3229             :     int nTileYCountZoomLevel0 =
    3230         187 :         std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
    3231             : 
    3232         374 :     const auto poTS = GetTilingScheme(m_osTilingScheme);
    3233         187 :     if (poTS)
    3234             :     {
    3235          20 :         CPLAssert(m_nZoomLevel >= 0);
    3236          20 :         m_dfTMSMinX = poTS->dfMinX;
    3237          20 :         m_dfTMSMaxY = poTS->dfMaxY;
    3238          20 :         dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
    3239          20 :         dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
    3240          20 :         nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
    3241          20 :         nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
    3242             :     }
    3243         187 :     m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
    3244         187 :     m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
    3245             : 
    3246         187 :     if (!ComputeTileAndPixelShifts())
    3247             :     {
    3248           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    3249             :                  "Overflow occurred in ComputeTileAndPixelShifts()");
    3250           1 :         return CE_Failure;
    3251             :     }
    3252             : 
    3253         186 :     if (!AllocCachedTiles())
    3254             :     {
    3255           0 :         return CE_Failure;
    3256             :     }
    3257             : 
    3258         186 :     double dfGDALMinX = m_gt[0];
    3259         186 :     double dfGDALMinY = m_gt[3] + nRasterYSize * m_gt[5];
    3260         186 :     double dfGDALMaxX = m_gt[0] + nRasterXSize * m_gt[1];
    3261         186 :     double dfGDALMaxY = m_gt[3];
    3262             : 
    3263         186 :     if (SoftStartTransaction() != OGRERR_NONE)
    3264           0 :         return CE_Failure;
    3265             : 
    3266             :     const char *pszCurrentDate =
    3267         186 :         CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
    3268             :     CPLString osInsertGpkgContentsFormatting(
    3269             :         "INSERT INTO gpkg_contents "
    3270             :         "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
    3271             :         "last_change,srs_id) VALUES "
    3272         372 :         "('%q','%q','%q','%q',%.17g,%.17g,%.17g,%.17g,");
    3273         186 :     osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
    3274         186 :     osInsertGpkgContentsFormatting += ",%d)";
    3275         372 :     char *pszSQL = sqlite3_mprintf(
    3276             :         osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
    3277         186 :         (m_eDT == GDT_UInt8) ? "tiles" : "2d-gridded-coverage",
    3278             :         m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
    3279             :         dfGDALMaxX, dfGDALMaxY,
    3280             :         pszCurrentDate ? pszCurrentDate
    3281             :                        : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
    3282             :         m_nSRID);
    3283             : 
    3284         186 :     eErr = SQLCommand(hDB, pszSQL);
    3285         186 :     sqlite3_free(pszSQL);
    3286         186 :     if (eErr != OGRERR_NONE)
    3287             :     {
    3288           8 :         SoftRollbackTransaction();
    3289           8 :         return CE_Failure;
    3290             :     }
    3291             : 
    3292         178 :     double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
    3293             :                                          dfPixelXSizeZoomLevel0;
    3294         178 :     double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
    3295             :                                          dfPixelYSizeZoomLevel0;
    3296             : 
    3297             :     pszSQL =
    3298         178 :         sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
    3299             :                         "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
    3300             :                         "('%q',%d,%.17g,%.17g,%.17g,%.17g)",
    3301             :                         m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
    3302             :                         dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
    3303         178 :     eErr = SQLCommand(hDB, pszSQL);
    3304         178 :     sqlite3_free(pszSQL);
    3305         178 :     if (eErr != OGRERR_NONE)
    3306             :     {
    3307           0 :         SoftRollbackTransaction();
    3308           0 :         return CE_Failure;
    3309             :     }
    3310             : 
    3311         178 :     m_apoOverviewDS.resize(m_nZoomLevel);
    3312             : 
    3313         591 :     for (int i = 0; i <= m_nZoomLevel; i++)
    3314             :     {
    3315         413 :         double dfPixelXSizeZoomLevel = 0.0;
    3316         413 :         double dfPixelYSizeZoomLevel = 0.0;
    3317         413 :         int nTileMatrixWidth = 0;
    3318         413 :         int nTileMatrixHeight = 0;
    3319         413 :         if (EQUAL(m_osTilingScheme, "CUSTOM"))
    3320             :         {
    3321         232 :             dfPixelXSizeZoomLevel = m_gt[1] * (1 << (m_nZoomLevel - i));
    3322         232 :             dfPixelYSizeZoomLevel = fabs(m_gt[5]) * (1 << (m_nZoomLevel - i));
    3323             :         }
    3324             :         else
    3325             :         {
    3326         181 :             dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
    3327         181 :             dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
    3328             :         }
    3329         413 :         nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
    3330         413 :         nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
    3331             : 
    3332         413 :         pszSQL = sqlite3_mprintf(
    3333             :             "INSERT INTO gpkg_tile_matrix "
    3334             :             "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
    3335             :             "height,pixel_x_size,pixel_y_size) VALUES "
    3336             :             "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
    3337             :             m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
    3338             :             nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
    3339             :             dfPixelYSizeZoomLevel);
    3340         413 :         eErr = SQLCommand(hDB, pszSQL);
    3341         413 :         sqlite3_free(pszSQL);
    3342         413 :         if (eErr != OGRERR_NONE)
    3343             :         {
    3344           0 :             SoftRollbackTransaction();
    3345           0 :             return CE_Failure;
    3346             :         }
    3347             : 
    3348         413 :         if (i < m_nZoomLevel)
    3349             :         {
    3350         470 :             auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
    3351         235 :             poOvrDS->ShareLockWithParentDataset(this);
    3352         235 :             poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
    3353             :                                 m_dfTMSMaxY, dfPixelXSizeZoomLevel,
    3354             :                                 dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
    3355             :                                 nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
    3356             :                                 dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
    3357             : 
    3358         235 :             m_apoOverviewDS[m_nZoomLevel - 1 - i] = std::move(poOvrDS);
    3359             :         }
    3360             :     }
    3361             : 
    3362         178 :     if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
    3363             :     {
    3364          40 :         eErr = SQLCommand(
    3365             :             hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
    3366          40 :         m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
    3367          40 :         if (eErr != OGRERR_NONE)
    3368             :         {
    3369           0 :             SoftRollbackTransaction();
    3370           0 :             return CE_Failure;
    3371             :         }
    3372             :     }
    3373             : 
    3374         178 :     SoftCommitTransaction();
    3375             : 
    3376         178 :     m_apoOverviewDS.resize(m_nZoomLevel);
    3377         178 :     m_bRecordInsertedInGPKGContent = true;
    3378             : 
    3379         178 :     return CE_None;
    3380             : }
    3381             : 
    3382             : /************************************************************************/
    3383             : /*                             FlushCache()                             */
    3384             : /************************************************************************/
    3385             : 
    3386        2862 : CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
    3387             : {
    3388        2862 :     if (m_bInFlushCache)
    3389           0 :         return CE_None;
    3390             : 
    3391        2862 :     if (eAccess == GA_Update || !m_bMetadataDirty)
    3392             :     {
    3393        2859 :         SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    3394             :     }
    3395             : 
    3396        2862 :     if (m_bRemoveOGREmptyTable)
    3397             :     {
    3398         781 :         m_bRemoveOGREmptyTable = false;
    3399         781 :         RemoveOGREmptyTable();
    3400             :     }
    3401             : 
    3402        2862 :     CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
    3403             : 
    3404        2862 :     FlushMetadata();
    3405             : 
    3406        2862 :     if (eAccess == GA_Update || !m_bMetadataDirty)
    3407             :     {
    3408             :         // Needed again as above IFlushCacheWithErrCode()
    3409             :         // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
    3410             :         // which modifies metadata
    3411        2862 :         SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    3412             :     }
    3413             : 
    3414        2862 :     return eErr;
    3415             : }
    3416             : 
    3417        5102 : CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
    3418             : 
    3419             : {
    3420        5102 :     if (m_bInFlushCache)
    3421        2173 :         return CE_None;
    3422        2929 :     m_bInFlushCache = true;
    3423        2929 :     if (hDB && eAccess == GA_ReadOnly && bAtClosing)
    3424             :     {
    3425             :         // Clean-up metadata that will go to PAM by removing items that
    3426             :         // are reconstructed.
    3427        2138 :         CPLStringList aosMD;
    3428        1707 :         for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
    3429             :              ++papszIter)
    3430             :         {
    3431         638 :             char *pszKey = nullptr;
    3432         638 :             CPLParseNameValue(*papszIter, &pszKey);
    3433        1276 :             if (pszKey &&
    3434         638 :                 (EQUAL(pszKey, "AREA_OR_POINT") ||
    3435         487 :                  EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
    3436         261 :                  EQUAL(pszKey, "ZOOM_LEVEL") ||
    3437         668 :                  STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
    3438             :             {
    3439             :                 // remove it
    3440             :             }
    3441             :             else
    3442             :             {
    3443          30 :                 aosMD.AddString(*papszIter);
    3444             :             }
    3445         638 :             CPLFree(pszKey);
    3446             :         }
    3447        1069 :         oMDMD.SetMetadata(aosMD.List());
    3448        1069 :         oMDMD.SetMetadata(nullptr, "IMAGE_STRUCTURE");
    3449             : 
    3450        2138 :         GDALPamDataset::FlushCache(bAtClosing);
    3451             :     }
    3452             :     else
    3453             :     {
    3454             :         // Short circuit GDALPamDataset to avoid serialization to .aux.xml
    3455        1860 :         GDALDataset::FlushCache(bAtClosing);
    3456             :     }
    3457             : 
    3458        7123 :     for (auto &poLayer : m_apoLayers)
    3459             :     {
    3460        4194 :         poLayer->RunDeferredCreationIfNecessary();
    3461        4194 :         poLayer->CreateSpatialIndexIfNecessary();
    3462             :     }
    3463             : 
    3464             :     // Update raster table last_change column in gpkg_contents if needed
    3465        2929 :     if (m_bHasModifiedTiles)
    3466             :     {
    3467         540 :         for (int i = 1; i <= nBands; ++i)
    3468             :         {
    3469             :             auto poBand =
    3470         359 :                 cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
    3471         359 :             if (!poBand->HaveStatsMetadataBeenSetInThisSession())
    3472             :             {
    3473         346 :                 poBand->InvalidateStatistics();
    3474         346 :                 if (psPam && psPam->pszPamFilename)
    3475         346 :                     VSIUnlink(psPam->pszPamFilename);
    3476             :             }
    3477             :         }
    3478             : 
    3479         181 :         UpdateGpkgContentsLastChange(m_osRasterTable);
    3480             : 
    3481         181 :         m_bHasModifiedTiles = false;
    3482             :     }
    3483             : 
    3484        2929 :     CPLErr eErr = FlushTiles();
    3485             : 
    3486        2929 :     m_bInFlushCache = false;
    3487        2929 :     return eErr;
    3488             : }
    3489             : 
    3490             : /************************************************************************/
    3491             : /*                      GetCurrentDateEscapedSQL()                      */
    3492             : /************************************************************************/
    3493             : 
    3494        2173 : std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
    3495             : {
    3496             :     const char *pszCurrentDate =
    3497        2173 :         CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
    3498        2173 :     if (pszCurrentDate)
    3499          10 :         return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
    3500        2168 :     return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
    3501             : }
    3502             : 
    3503             : /************************************************************************/
    3504             : /*                    UpdateGpkgContentsLastChange()                    */
    3505             : /************************************************************************/
    3506             : 
    3507             : OGRErr
    3508         952 : GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
    3509             : {
    3510             :     char *pszSQL =
    3511         952 :         sqlite3_mprintf("UPDATE gpkg_contents SET "
    3512             :                         "last_change = %s "
    3513             :                         "WHERE lower(table_name) = lower('%q')",
    3514        1904 :                         GetCurrentDateEscapedSQL().c_str(), pszTableName);
    3515         952 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
    3516         952 :     sqlite3_free(pszSQL);
    3517         952 :     return eErr;
    3518             : }
    3519             : 
    3520             : /************************************************************************/
    3521             : /*                          IBuildOverviews()                           */
    3522             : /************************************************************************/
    3523             : 
    3524          20 : CPLErr GDALGeoPackageDataset::IBuildOverviews(
    3525             :     const char *pszResampling, int nOverviews, const int *panOverviewList,
    3526             :     int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
    3527             :     void *pProgressData, CSLConstList papszOptions)
    3528             : {
    3529          20 :     if (GetAccess() != GA_Update)
    3530             :     {
    3531           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3532             :                  "Overview building not supported on a database opened in "
    3533             :                  "read-only mode");
    3534           1 :         return CE_Failure;
    3535             :     }
    3536          19 :     if (m_poParentDS != nullptr)
    3537             :     {
    3538           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3539             :                  "Overview building not supported on overview dataset");
    3540           1 :         return CE_Failure;
    3541             :     }
    3542             : 
    3543          18 :     if (nOverviews == 0)
    3544             :     {
    3545           5 :         for (auto &poOvrDS : m_apoOverviewDS)
    3546           3 :             poOvrDS->FlushCache(false);
    3547             : 
    3548           2 :         SoftStartTransaction();
    3549             : 
    3550           2 :         if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
    3551             :         {
    3552           1 :             char *pszSQL = sqlite3_mprintf(
    3553             :                 "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
    3554             :                 "(SELECT y.id FROM \"%w\" x "
    3555             :                 "JOIN gpkg_2d_gridded_tile_ancillary y "
    3556             :                 "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
    3557             :                 "x.zoom_level < %d)",
    3558             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
    3559           1 :             OGRErr eErr = SQLCommand(hDB, pszSQL);
    3560           1 :             sqlite3_free(pszSQL);
    3561           1 :             if (eErr != OGRERR_NONE)
    3562             :             {
    3563           0 :                 SoftRollbackTransaction();
    3564           0 :                 return CE_Failure;
    3565             :             }
    3566             :         }
    3567             : 
    3568             :         char *pszSQL =
    3569           2 :             sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
    3570             :                             m_osRasterTable.c_str(), m_nZoomLevel);
    3571           2 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
    3572           2 :         sqlite3_free(pszSQL);
    3573           2 :         if (eErr != OGRERR_NONE)
    3574             :         {
    3575           0 :             SoftRollbackTransaction();
    3576           0 :             return CE_Failure;
    3577             :         }
    3578             : 
    3579           2 :         SoftCommitTransaction();
    3580             : 
    3581           2 :         return CE_None;
    3582             :     }
    3583             : 
    3584          16 :     if (nBandsIn != nBands)
    3585             :     {
    3586           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3587             :                  "Generation of overviews in GPKG only"
    3588             :                  "supported when operating on all bands.");
    3589           0 :         return CE_Failure;
    3590             :     }
    3591             : 
    3592          16 :     if (m_apoOverviewDS.empty())
    3593             :     {
    3594           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3595             :                  "Image too small to support overviews");
    3596           0 :         return CE_Failure;
    3597             :     }
    3598             : 
    3599          16 :     FlushCache(false);
    3600          60 :     for (int i = 0; i < nOverviews; i++)
    3601             :     {
    3602          47 :         if (panOverviewList[i] < 2)
    3603             :         {
    3604           1 :             CPLError(CE_Failure, CPLE_IllegalArg,
    3605             :                      "Overview factor must be >= 2");
    3606           1 :             return CE_Failure;
    3607             :         }
    3608             : 
    3609          46 :         bool bFound = false;
    3610          46 :         int jCandidate = -1;
    3611          46 :         int nMaxOvFactor = 0;
    3612         196 :         for (int j = 0; j < static_cast<int>(m_apoOverviewDS.size()); j++)
    3613             :         {
    3614         190 :             const auto poODS = m_apoOverviewDS[j].get();
    3615             :             const int nOvFactor =
    3616         190 :                 static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
    3617             : 
    3618         190 :             nMaxOvFactor = nOvFactor;
    3619             : 
    3620         190 :             if (nOvFactor == panOverviewList[i])
    3621             :             {
    3622          40 :                 bFound = true;
    3623          40 :                 break;
    3624             :             }
    3625             : 
    3626         150 :             if (jCandidate < 0 && nOvFactor > panOverviewList[i])
    3627           1 :                 jCandidate = j;
    3628             :         }
    3629             : 
    3630          46 :         if (!bFound)
    3631             :         {
    3632             :             /* Mostly for debug */
    3633           6 :             if (!CPLTestBool(CPLGetConfigOption(
    3634             :                     "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
    3635             :             {
    3636           2 :                 CPLString osOvrList;
    3637           4 :                 for (const auto &poODS : m_apoOverviewDS)
    3638             :                 {
    3639             :                     const int nOvFactor =
    3640           2 :                         static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
    3641             : 
    3642           2 :                     if (!osOvrList.empty())
    3643           0 :                         osOvrList += ' ';
    3644           2 :                     osOvrList += CPLSPrintf("%d", nOvFactor);
    3645             :                 }
    3646           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    3647             :                          "Only overviews %s can be computed",
    3648             :                          osOvrList.c_str());
    3649           2 :                 return CE_Failure;
    3650             :             }
    3651             :             else
    3652             :             {
    3653           4 :                 int nOvFactor = panOverviewList[i];
    3654           4 :                 if (jCandidate < 0)
    3655           3 :                     jCandidate = static_cast<int>(m_apoOverviewDS.size());
    3656             : 
    3657           4 :                 int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
    3658           4 :                 int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
    3659           4 :                 if (!(jCandidate == static_cast<int>(m_apoOverviewDS.size()) &&
    3660           5 :                       nOvFactor == 2 * nMaxOvFactor) &&
    3661           1 :                     !m_bZoomOther)
    3662             :                 {
    3663           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    3664             :                              "Use of overview factor %d causes gpkg_zoom_other "
    3665             :                              "extension to be needed",
    3666             :                              nOvFactor);
    3667           1 :                     RegisterZoomOtherExtension();
    3668           1 :                     m_bZoomOther = true;
    3669             :                 }
    3670             : 
    3671           4 :                 SoftStartTransaction();
    3672             : 
    3673           4 :                 CPLAssert(jCandidate > 0);
    3674             :                 const int nNewZoomLevel =
    3675           4 :                     m_apoOverviewDS[jCandidate - 1]->m_nZoomLevel;
    3676             : 
    3677             :                 char *pszSQL;
    3678             :                 OGRErr eErr;
    3679          24 :                 for (int k = 0; k <= jCandidate; k++)
    3680             :                 {
    3681          60 :                     pszSQL = sqlite3_mprintf(
    3682             :                         "UPDATE gpkg_tile_matrix SET zoom_level = %d "
    3683             :                         "WHERE lower(table_name) = lower('%q') AND zoom_level "
    3684             :                         "= %d",
    3685          20 :                         m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
    3686          20 :                         m_nZoomLevel - k);
    3687          20 :                     eErr = SQLCommand(hDB, pszSQL);
    3688          20 :                     sqlite3_free(pszSQL);
    3689          20 :                     if (eErr != OGRERR_NONE)
    3690             :                     {
    3691           0 :                         SoftRollbackTransaction();
    3692           0 :                         return CE_Failure;
    3693             :                     }
    3694             : 
    3695             :                     pszSQL =
    3696          20 :                         sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
    3697             :                                         "WHERE zoom_level = %d",
    3698             :                                         m_osRasterTable.c_str(),
    3699          20 :                                         m_nZoomLevel - k + 1, m_nZoomLevel - k);
    3700          20 :                     eErr = SQLCommand(hDB, pszSQL);
    3701          20 :                     sqlite3_free(pszSQL);
    3702          20 :                     if (eErr != OGRERR_NONE)
    3703             :                     {
    3704           0 :                         SoftRollbackTransaction();
    3705           0 :                         return CE_Failure;
    3706             :                     }
    3707             :                 }
    3708             : 
    3709           4 :                 double dfGDALMinX = m_gt[0];
    3710           4 :                 double dfGDALMinY = m_gt[3] + nRasterYSize * m_gt[5];
    3711           4 :                 double dfGDALMaxX = m_gt[0] + nRasterXSize * m_gt[1];
    3712           4 :                 double dfGDALMaxY = m_gt[3];
    3713           4 :                 double dfPixelXSizeZoomLevel = m_gt[1] * nOvFactor;
    3714           4 :                 double dfPixelYSizeZoomLevel = fabs(m_gt[5]) * nOvFactor;
    3715             :                 int nTileWidth, nTileHeight;
    3716           4 :                 GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3717           4 :                 int nTileMatrixWidth = DIV_ROUND_UP(nOvXSize, nTileWidth);
    3718           4 :                 int nTileMatrixHeight = DIV_ROUND_UP(nOvYSize, nTileHeight);
    3719           4 :                 pszSQL = sqlite3_mprintf(
    3720             :                     "INSERT INTO gpkg_tile_matrix "
    3721             :                     "(table_name,zoom_level,matrix_width,matrix_height,tile_"
    3722             :                     "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
    3723             :                     "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
    3724             :                     m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
    3725             :                     nTileMatrixHeight, nTileWidth, nTileHeight,
    3726             :                     dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
    3727           4 :                 eErr = SQLCommand(hDB, pszSQL);
    3728           4 :                 sqlite3_free(pszSQL);
    3729           4 :                 if (eErr != OGRERR_NONE)
    3730             :                 {
    3731           0 :                     SoftRollbackTransaction();
    3732           0 :                     return CE_Failure;
    3733             :                 }
    3734             : 
    3735           4 :                 SoftCommitTransaction();
    3736             : 
    3737           4 :                 m_nZoomLevel++; /* this change our zoom level as well as
    3738             :                                    previous overviews */
    3739          20 :                 for (int k = 0; k < jCandidate; k++)
    3740          16 :                     m_apoOverviewDS[k]->m_nZoomLevel++;
    3741             : 
    3742           4 :                 auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
    3743           4 :                 poOvrDS->ShareLockWithParentDataset(this);
    3744           4 :                 poOvrDS->InitRaster(
    3745             :                     this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
    3746             :                     m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
    3747             :                     nTileWidth, nTileHeight, nTileMatrixWidth,
    3748             :                     nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
    3749             :                     dfGDALMaxY);
    3750           4 :                 m_apoOverviewDS.insert(m_apoOverviewDS.begin() + jCandidate,
    3751           8 :                                        std::move(poOvrDS));
    3752             :             }
    3753             :         }
    3754             :     }
    3755             : 
    3756             :     GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
    3757          13 :         CPLCalloc(sizeof(GDALRasterBand **), nBands));
    3758          13 :     CPLErr eErr = CE_None;
    3759          49 :     for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
    3760             :     {
    3761          72 :         papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
    3762          36 :             CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
    3763          36 :         int iCurOverview = 0;
    3764         185 :         for (int i = 0; i < nOverviews; i++)
    3765             :         {
    3766         149 :             bool bFound = false;
    3767         724 :             for (const auto &poODS : m_apoOverviewDS)
    3768             :             {
    3769             :                 const int nOvFactor =
    3770         724 :                     static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
    3771             : 
    3772         724 :                 if (nOvFactor == panOverviewList[i])
    3773             :                 {
    3774         298 :                     papapoOverviewBands[iBand][iCurOverview] =
    3775         149 :                         poODS->GetRasterBand(iBand + 1);
    3776         149 :                     iCurOverview++;
    3777         149 :                     bFound = true;
    3778         149 :                     break;
    3779             :                 }
    3780             :             }
    3781         149 :             if (!bFound)
    3782             :             {
    3783           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3784             :                          "Could not find dataset corresponding to ov factor %d",
    3785           0 :                          panOverviewList[i]);
    3786           0 :                 eErr = CE_Failure;
    3787             :             }
    3788             :         }
    3789          36 :         if (eErr == CE_None)
    3790             :         {
    3791          36 :             CPLAssert(iCurOverview == nOverviews);
    3792             :         }
    3793             :     }
    3794             : 
    3795          13 :     if (eErr == CE_None)
    3796          13 :         eErr = GDALRegenerateOverviewsMultiBand(
    3797          13 :             nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
    3798             :             pfnProgress, pProgressData, papszOptions);
    3799             : 
    3800          49 :     for (int iBand = 0; iBand < nBands; iBand++)
    3801             :     {
    3802          36 :         CPLFree(papapoOverviewBands[iBand]);
    3803             :     }
    3804          13 :     CPLFree(papapoOverviewBands);
    3805             : 
    3806          13 :     return eErr;
    3807             : }
    3808             : 
    3809             : /************************************************************************/
    3810             : /*                            GetFileList()                             */
    3811             : /************************************************************************/
    3812             : 
    3813          38 : char **GDALGeoPackageDataset::GetFileList()
    3814             : {
    3815          38 :     TryLoadXML();
    3816          38 :     return GDALPamDataset::GetFileList();
    3817             : }
    3818             : 
    3819             : /************************************************************************/
    3820             : /*                       GetMetadataDomainList()                        */
    3821             : /************************************************************************/
    3822             : 
    3823          47 : char **GDALGeoPackageDataset::GetMetadataDomainList()
    3824             : {
    3825          47 :     GetMetadata();
    3826          47 :     if (!m_osRasterTable.empty())
    3827           5 :         GetMetadata("GEOPACKAGE");
    3828          47 :     return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
    3829          47 :                                    TRUE, "SUBDATASETS", nullptr);
    3830             : }
    3831             : 
    3832             : /************************************************************************/
    3833             : /*                        CheckMetadataDomain()                         */
    3834             : /************************************************************************/
    3835             : 
    3836        6071 : const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
    3837             : {
    3838        6256 :     if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
    3839         185 :         m_osRasterTable.empty())
    3840             :     {
    3841           4 :         CPLError(
    3842             :             CE_Warning, CPLE_IllegalArg,
    3843             :             "Using GEOPACKAGE for a non-raster geopackage is not supported. "
    3844             :             "Using default domain instead");
    3845           4 :         return nullptr;
    3846             :     }
    3847        6067 :     return pszDomain;
    3848             : }
    3849             : 
    3850             : /************************************************************************/
    3851             : /*                         HasMetadataTables()                          */
    3852             : /************************************************************************/
    3853             : 
    3854        5768 : bool GDALGeoPackageDataset::HasMetadataTables() const
    3855             : {
    3856        5768 :     if (m_nHasMetadataTables < 0)
    3857             :     {
    3858             :         const int nCount =
    3859        2210 :             SQLGetInteger(hDB,
    3860             :                           "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
    3861             :                           "('gpkg_metadata', 'gpkg_metadata_reference') "
    3862             :                           "AND type IN ('table', 'view')",
    3863             :                           nullptr);
    3864        2210 :         m_nHasMetadataTables = nCount == 2;
    3865             :     }
    3866        5768 :     return CPL_TO_BOOL(m_nHasMetadataTables);
    3867             : }
    3868             : 
    3869             : /************************************************************************/
    3870             : /*                        HasDataColumnsTable()                         */
    3871             : /************************************************************************/
    3872             : 
    3873        1291 : bool GDALGeoPackageDataset::HasDataColumnsTable() const
    3874             : {
    3875        2582 :     const int nCount = SQLGetInteger(
    3876        1291 :         hDB,
    3877             :         "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
    3878             :         "AND type IN ('table', 'view')",
    3879             :         nullptr);
    3880        1291 :     return nCount == 1;
    3881             : }
    3882             : 
    3883             : /************************************************************************/
    3884             : /*                   HasDataColumnConstraintsTable()                    */
    3885             : /************************************************************************/
    3886             : 
    3887         157 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
    3888             : {
    3889         157 :     const int nCount = SQLGetInteger(hDB,
    3890             :                                      "SELECT 1 FROM sqlite_master WHERE name = "
    3891             :                                      "'gpkg_data_column_constraints'"
    3892             :                                      "AND type IN ('table', 'view')",
    3893             :                                      nullptr);
    3894         157 :     return nCount == 1;
    3895             : }
    3896             : 
    3897             : /************************************************************************/
    3898             : /*               HasDataColumnConstraintsTableGPKG_1_0()                */
    3899             : /************************************************************************/
    3900             : 
    3901         109 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
    3902             : {
    3903         109 :     if (m_nApplicationId != GP10_APPLICATION_ID)
    3904         107 :         return false;
    3905             :     // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
    3906             :     // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
    3907           2 :     bool bRet = false;
    3908           2 :     sqlite3_stmt *hSQLStmt = nullptr;
    3909           2 :     int rc = sqlite3_prepare_v2(hDB,
    3910             :                                 "SELECT minIsInclusive, maxIsInclusive FROM "
    3911             :                                 "gpkg_data_column_constraints",
    3912             :                                 -1, &hSQLStmt, nullptr);
    3913           2 :     if (rc == SQLITE_OK)
    3914             :     {
    3915           2 :         bRet = true;
    3916           2 :         sqlite3_finalize(hSQLStmt);
    3917             :     }
    3918           2 :     return bRet;
    3919             : }
    3920             : 
    3921             : /************************************************************************/
    3922             : /*      CreateColumnsTableAndColumnConstraintsTablesIfNecessary()       */
    3923             : /************************************************************************/
    3924             : 
    3925          50 : bool GDALGeoPackageDataset::
    3926             :     CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
    3927             : {
    3928          50 :     if (!HasDataColumnsTable())
    3929             :     {
    3930             :         // Geopackage < 1.3 had
    3931             :         // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
    3932             :         // gpkg_contents(table_name) instead of the unique constraint.
    3933          10 :         if (OGRERR_NONE !=
    3934          10 :             SQLCommand(
    3935             :                 GetDB(),
    3936             :                 "CREATE TABLE gpkg_data_columns ("
    3937             :                 "table_name TEXT NOT NULL,"
    3938             :                 "column_name TEXT NOT NULL,"
    3939             :                 "name TEXT,"
    3940             :                 "title TEXT,"
    3941             :                 "description TEXT,"
    3942             :                 "mime_type TEXT,"
    3943             :                 "constraint_name TEXT,"
    3944             :                 "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
    3945             :                 "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
    3946             :         {
    3947           0 :             return false;
    3948             :         }
    3949             :     }
    3950          50 :     if (!HasDataColumnConstraintsTable())
    3951             :     {
    3952          22 :         const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
    3953          11 :                                            ? "min_is_inclusive"
    3954             :                                            : "minIsInclusive";
    3955          22 :         const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
    3956          11 :                                            ? "max_is_inclusive"
    3957             :                                            : "maxIsInclusive";
    3958             : 
    3959             :         const std::string osSQL(
    3960             :             CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
    3961             :                        "constraint_name TEXT NOT NULL,"
    3962             :                        "constraint_type TEXT NOT NULL,"
    3963             :                        "value TEXT,"
    3964             :                        "min NUMERIC,"
    3965             :                        "%s BOOLEAN,"
    3966             :                        "max NUMERIC,"
    3967             :                        "%s BOOLEAN,"
    3968             :                        "description TEXT,"
    3969             :                        "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
    3970             :                        "constraint_type, value));",
    3971          11 :                        min_is_inclusive, max_is_inclusive));
    3972          11 :         if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
    3973             :         {
    3974           0 :             return false;
    3975             :         }
    3976             :     }
    3977          50 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    3978             :     {
    3979           0 :         return false;
    3980             :     }
    3981          50 :     if (SQLGetInteger(GetDB(),
    3982             :                       "SELECT 1 FROM gpkg_extensions WHERE "
    3983             :                       "table_name = 'gpkg_data_columns'",
    3984          50 :                       nullptr) != 1)
    3985             :     {
    3986          11 :         if (OGRERR_NONE !=
    3987          11 :             SQLCommand(
    3988             :                 GetDB(),
    3989             :                 "INSERT INTO gpkg_extensions "
    3990             :                 "(table_name,column_name,extension_name,definition,scope) "
    3991             :                 "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
    3992             :                 "'http://www.geopackage.org/spec121/#extension_schema', "
    3993             :                 "'read-write')"))
    3994             :         {
    3995           0 :             return false;
    3996             :         }
    3997             :     }
    3998          50 :     if (SQLGetInteger(GetDB(),
    3999             :                       "SELECT 1 FROM gpkg_extensions WHERE "
    4000             :                       "table_name = 'gpkg_data_column_constraints'",
    4001          50 :                       nullptr) != 1)
    4002             :     {
    4003          11 :         if (OGRERR_NONE !=
    4004          11 :             SQLCommand(
    4005             :                 GetDB(),
    4006             :                 "INSERT INTO gpkg_extensions "
    4007             :                 "(table_name,column_name,extension_name,definition,scope) "
    4008             :                 "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
    4009             :                 "'http://www.geopackage.org/spec121/#extension_schema', "
    4010             :                 "'read-write')"))
    4011             :         {
    4012           0 :             return false;
    4013             :         }
    4014             :     }
    4015             : 
    4016          50 :     return true;
    4017             : }
    4018             : 
    4019             : /************************************************************************/
    4020             : /*                      HasGpkgextRelationsTable()                      */
    4021             : /************************************************************************/
    4022             : 
    4023        1287 : bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
    4024             : {
    4025        2574 :     const int nCount = SQLGetInteger(
    4026        1287 :         hDB,
    4027             :         "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
    4028             :         "AND type IN ('table', 'view')",
    4029             :         nullptr);
    4030        1287 :     return nCount == 1;
    4031             : }
    4032             : 
    4033             : /************************************************************************/
    4034             : /*                  CreateRelationsTableIfNecessary()                   */
    4035             : /************************************************************************/
    4036             : 
    4037           9 : bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
    4038             : {
    4039           9 :     if (HasGpkgextRelationsTable())
    4040             :     {
    4041           5 :         return true;
    4042             :     }
    4043             : 
    4044           4 :     if (OGRERR_NONE !=
    4045           4 :         SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
    4046             :                             "id INTEGER PRIMARY KEY AUTOINCREMENT,"
    4047             :                             "base_table_name TEXT NOT NULL,"
    4048             :                             "base_primary_column TEXT NOT NULL DEFAULT 'id',"
    4049             :                             "related_table_name TEXT NOT NULL,"
    4050             :                             "related_primary_column TEXT NOT NULL DEFAULT 'id',"
    4051             :                             "relation_name TEXT NOT NULL,"
    4052             :                             "mapping_table_name TEXT NOT NULL UNIQUE);"))
    4053             :     {
    4054           0 :         return false;
    4055             :     }
    4056             : 
    4057           4 :     return true;
    4058             : }
    4059             : 
    4060             : /************************************************************************/
    4061             : /*                         HasQGISLayerStyles()                         */
    4062             : /************************************************************************/
    4063             : 
    4064          11 : bool GDALGeoPackageDataset::HasQGISLayerStyles() const
    4065             : {
    4066             :     // QGIS layer_styles extension:
    4067             :     // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
    4068          11 :     bool bRet = false;
    4069             :     const int nCount =
    4070          11 :         SQLGetInteger(hDB,
    4071             :                       "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
    4072             :                       "AND type = 'table'",
    4073             :                       nullptr);
    4074          11 :     if (nCount == 1)
    4075             :     {
    4076           1 :         sqlite3_stmt *hSQLStmt = nullptr;
    4077           2 :         int rc = sqlite3_prepare_v2(
    4078           1 :             hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
    4079             :             &hSQLStmt, nullptr);
    4080           1 :         if (rc == SQLITE_OK)
    4081             :         {
    4082           1 :             bRet = true;
    4083           1 :             sqlite3_finalize(hSQLStmt);
    4084             :         }
    4085             :     }
    4086          11 :     return bRet;
    4087             : }
    4088             : 
    4089             : /************************************************************************/
    4090             : /*                            GetMetadata()                             */
    4091             : /************************************************************************/
    4092             : 
    4093        3990 : CSLConstList GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
    4094             : 
    4095             : {
    4096        3990 :     pszDomain = CheckMetadataDomain(pszDomain);
    4097        3990 :     if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
    4098          74 :         return m_aosSubDatasets.List();
    4099             : 
    4100        3916 :     if (m_bHasReadMetadataFromStorage)
    4101        1773 :         return GDALPamDataset::GetMetadata(pszDomain);
    4102             : 
    4103        2143 :     m_bHasReadMetadataFromStorage = true;
    4104             : 
    4105        2143 :     TryLoadXML();
    4106             : 
    4107        2143 :     if (!HasMetadataTables())
    4108        1626 :         return GDALPamDataset::GetMetadata(pszDomain);
    4109             : 
    4110         517 :     char *pszSQL = nullptr;
    4111         517 :     if (!m_osRasterTable.empty())
    4112             :     {
    4113         175 :         pszSQL = sqlite3_mprintf(
    4114             :             "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
    4115             :             "mdr.reference_scope FROM gpkg_metadata md "
    4116             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4117             :             "WHERE "
    4118             :             "(mdr.reference_scope = 'geopackage' OR "
    4119             :             "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
    4120             :             "lower('%q'))) ORDER BY md.id "
    4121             :             "LIMIT 1000",  // to avoid denial of service
    4122             :             m_osRasterTable.c_str());
    4123             :     }
    4124             :     else
    4125             :     {
    4126         342 :         pszSQL = sqlite3_mprintf(
    4127             :             "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
    4128             :             "mdr.reference_scope FROM gpkg_metadata md "
    4129             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4130             :             "WHERE "
    4131             :             "mdr.reference_scope = 'geopackage' ORDER BY md.id "
    4132             :             "LIMIT 1000"  // to avoid denial of service
    4133             :         );
    4134             :     }
    4135             : 
    4136        1034 :     auto oResult = SQLQuery(hDB, pszSQL);
    4137         517 :     sqlite3_free(pszSQL);
    4138         517 :     if (!oResult)
    4139             :     {
    4140           0 :         return GDALPamDataset::GetMetadata(pszDomain);
    4141             :     }
    4142             : 
    4143         517 :     char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
    4144             : 
    4145             :     /* GDAL metadata */
    4146         712 :     for (int i = 0; i < oResult->RowCount(); i++)
    4147             :     {
    4148         195 :         const char *pszMetadata = oResult->GetValue(0, i);
    4149         195 :         const char *pszMDStandardURI = oResult->GetValue(1, i);
    4150         195 :         const char *pszMimeType = oResult->GetValue(2, i);
    4151         195 :         const char *pszReferenceScope = oResult->GetValue(3, i);
    4152         195 :         if (pszMetadata && pszMDStandardURI && pszMimeType &&
    4153         195 :             pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
    4154         179 :             EQUAL(pszMimeType, "text/xml"))
    4155             :         {
    4156         179 :             CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
    4157         179 :             if (psXMLNode)
    4158             :             {
    4159         358 :                 GDALMultiDomainMetadata oLocalMDMD;
    4160         179 :                 oLocalMDMD.XMLInit(psXMLNode, FALSE);
    4161         343 :                 if (!m_osRasterTable.empty() &&
    4162         164 :                     EQUAL(pszReferenceScope, "geopackage"))
    4163             :                 {
    4164           6 :                     oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
    4165             :                 }
    4166             :                 else
    4167             :                 {
    4168             :                     papszMetadata =
    4169         173 :                         CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
    4170         173 :                     CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
    4171         173 :                     CSLConstList papszIter = papszDomainList;
    4172         462 :                     while (papszIter && *papszIter)
    4173             :                     {
    4174         289 :                         if (EQUAL(*papszIter, "IMAGE_STRUCTURE"))
    4175             :                         {
    4176             :                             CSLConstList papszMD =
    4177         131 :                                 oLocalMDMD.GetMetadata(*papszIter);
    4178             :                             const char *pszBAND_COUNT =
    4179         131 :                                 CSLFetchNameValue(papszMD, "BAND_COUNT");
    4180         131 :                             if (pszBAND_COUNT)
    4181         129 :                                 m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
    4182             : 
    4183             :                             const char *pszCOLOR_TABLE =
    4184         131 :                                 CSLFetchNameValue(papszMD, "COLOR_TABLE");
    4185         131 :                             if (pszCOLOR_TABLE)
    4186             :                             {
    4187             :                                 const CPLStringList aosTokens(
    4188             :                                     CSLTokenizeString2(pszCOLOR_TABLE, "{,",
    4189          26 :                                                        0));
    4190          13 :                                 if ((aosTokens.size() % 4) == 0)
    4191             :                                 {
    4192          13 :                                     const int nColors = aosTokens.size() / 4;
    4193             :                                     m_poCTFromMetadata =
    4194          13 :                                         std::make_unique<GDALColorTable>();
    4195        3341 :                                     for (int iColor = 0; iColor < nColors;
    4196             :                                          ++iColor)
    4197             :                                     {
    4198             :                                         GDALColorEntry sEntry;
    4199        3328 :                                         sEntry.c1 = static_cast<short>(
    4200        3328 :                                             atoi(aosTokens[4 * iColor + 0]));
    4201        3328 :                                         sEntry.c2 = static_cast<short>(
    4202        3328 :                                             atoi(aosTokens[4 * iColor + 1]));
    4203        3328 :                                         sEntry.c3 = static_cast<short>(
    4204        3328 :                                             atoi(aosTokens[4 * iColor + 2]));
    4205        3328 :                                         sEntry.c4 = static_cast<short>(
    4206        3328 :                                             atoi(aosTokens[4 * iColor + 3]));
    4207        3328 :                                         m_poCTFromMetadata->SetColorEntry(
    4208             :                                             iColor, &sEntry);
    4209             :                                     }
    4210             :                                 }
    4211             :                             }
    4212             : 
    4213             :                             const char *pszTILE_FORMAT =
    4214         131 :                                 CSLFetchNameValue(papszMD, "TILE_FORMAT");
    4215         131 :                             if (pszTILE_FORMAT)
    4216             :                             {
    4217           8 :                                 m_osTFFromMetadata = pszTILE_FORMAT;
    4218           8 :                                 oMDMD.SetMetadataItem("TILE_FORMAT",
    4219             :                                                       pszTILE_FORMAT,
    4220             :                                                       "IMAGE_STRUCTURE");
    4221             :                             }
    4222             : 
    4223             :                             const char *pszNodataValue =
    4224         131 :                                 CSLFetchNameValue(papszMD, "NODATA_VALUE");
    4225         131 :                             if (pszNodataValue)
    4226             :                             {
    4227           2 :                                 m_osNodataValueFromMetadata = pszNodataValue;
    4228             :                             }
    4229             :                         }
    4230             : 
    4231         158 :                         else if (!EQUAL(*papszIter, "") &&
    4232          16 :                                  !STARTS_WITH(*papszIter, "BAND_"))
    4233             :                         {
    4234          12 :                             oMDMD.SetMetadata(
    4235           6 :                                 oLocalMDMD.GetMetadata(*papszIter), *papszIter);
    4236             :                         }
    4237         289 :                         papszIter++;
    4238             :                     }
    4239             :                 }
    4240         179 :                 CPLDestroyXMLNode(psXMLNode);
    4241             :             }
    4242             :         }
    4243             :     }
    4244             : 
    4245         517 :     GDALPamDataset::SetMetadata(papszMetadata);
    4246         517 :     CSLDestroy(papszMetadata);
    4247         517 :     papszMetadata = nullptr;
    4248             : 
    4249             :     /* Add non-GDAL metadata now */
    4250         517 :     int nNonGDALMDILocal = 1;
    4251         517 :     int nNonGDALMDIGeopackage = 1;
    4252         712 :     for (int i = 0; i < oResult->RowCount(); i++)
    4253             :     {
    4254         195 :         const char *pszMetadata = oResult->GetValue(0, i);
    4255         195 :         const char *pszMDStandardURI = oResult->GetValue(1, i);
    4256         195 :         const char *pszMimeType = oResult->GetValue(2, i);
    4257         195 :         const char *pszReferenceScope = oResult->GetValue(3, i);
    4258         195 :         if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
    4259         195 :             pszMimeType == nullptr || pszReferenceScope == nullptr)
    4260             :         {
    4261             :             // should not happen as there are NOT NULL constraints
    4262             :             // But a database could lack such NOT NULL constraints or have
    4263             :             // large values that would cause a memory allocation failure.
    4264           0 :             continue;
    4265             :         }
    4266         195 :         int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
    4267         195 :         if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
    4268         179 :             EQUAL(pszMimeType, "text/xml"))
    4269         179 :             continue;
    4270             : 
    4271          16 :         if (!m_osRasterTable.empty() && bIsGPKGScope)
    4272             :         {
    4273           8 :             oMDMD.SetMetadataItem(
    4274             :                 CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
    4275             :                 pszMetadata, "GEOPACKAGE");
    4276           8 :             nNonGDALMDIGeopackage++;
    4277             :         }
    4278             :         /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
    4279             :         ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
    4280             :         {
    4281             :             char* apszMD[2];
    4282             :             apszMD[0] = (char*)pszMetadata;
    4283             :             apszMD[1] = NULL;
    4284             :             oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
    4285             :         }*/
    4286             :         else
    4287             :         {
    4288           8 :             oMDMD.SetMetadataItem(
    4289             :                 CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
    4290             :                 pszMetadata);
    4291           8 :             nNonGDALMDILocal++;
    4292             :         }
    4293             :     }
    4294             : 
    4295         517 :     return GDALPamDataset::GetMetadata(pszDomain);
    4296             : }
    4297             : 
    4298             : /************************************************************************/
    4299             : /*                           WriteMetadata()                            */
    4300             : /************************************************************************/
    4301             : 
    4302         735 : void GDALGeoPackageDataset::WriteMetadata(
    4303             :     CPLXMLNode *psXMLNode, /* will be destroyed by the method */
    4304             :     const char *pszTableName)
    4305             : {
    4306         735 :     const bool bIsEmpty = (psXMLNode == nullptr);
    4307         735 :     if (!HasMetadataTables())
    4308             :     {
    4309         534 :         if (bIsEmpty || !CreateMetadataTables())
    4310             :         {
    4311         244 :             CPLDestroyXMLNode(psXMLNode);
    4312         244 :             return;
    4313             :         }
    4314             :     }
    4315             : 
    4316         491 :     char *pszXML = nullptr;
    4317         491 :     if (!bIsEmpty)
    4318             :     {
    4319             :         CPLXMLNode *psMasterXMLNode =
    4320         338 :             CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
    4321         338 :         psMasterXMLNode->psChild = psXMLNode;
    4322         338 :         pszXML = CPLSerializeXMLTree(psMasterXMLNode);
    4323         338 :         CPLDestroyXMLNode(psMasterXMLNode);
    4324             :     }
    4325             :     // cppcheck-suppress uselessAssignmentPtrArg
    4326         491 :     psXMLNode = nullptr;
    4327             : 
    4328         491 :     char *pszSQL = nullptr;
    4329         491 :     if (pszTableName && pszTableName[0] != '\0')
    4330             :     {
    4331         346 :         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 = 'table' AND "
    4337             :             "lower(mdr.table_name) = lower('%q')",
    4338             :             pszTableName);
    4339             :     }
    4340             :     else
    4341             :     {
    4342         145 :         pszSQL = sqlite3_mprintf(
    4343             :             "SELECT md.id FROM gpkg_metadata md "
    4344             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4345             :             "WHERE md.md_scope = 'dataset' AND "
    4346             :             "md.md_standard_uri='http://gdal.org' "
    4347             :             "AND md.mime_type='text/xml' AND mdr.reference_scope = "
    4348             :             "'geopackage'");
    4349             :     }
    4350             :     OGRErr err;
    4351         491 :     int mdId = SQLGetInteger(hDB, pszSQL, &err);
    4352         491 :     if (err != OGRERR_NONE)
    4353         459 :         mdId = -1;
    4354         491 :     sqlite3_free(pszSQL);
    4355             : 
    4356         491 :     if (bIsEmpty)
    4357             :     {
    4358         153 :         if (mdId >= 0)
    4359             :         {
    4360           6 :             SQLCommand(
    4361             :                 hDB,
    4362             :                 CPLSPrintf(
    4363             :                     "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
    4364             :                     mdId));
    4365           6 :             SQLCommand(
    4366             :                 hDB,
    4367             :                 CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
    4368             :         }
    4369             :     }
    4370             :     else
    4371             :     {
    4372         338 :         if (mdId >= 0)
    4373             :         {
    4374          26 :             pszSQL = sqlite3_mprintf(
    4375             :                 "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
    4376             :                 pszXML, mdId);
    4377             :         }
    4378             :         else
    4379             :         {
    4380             :             pszSQL =
    4381         312 :                 sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
    4382             :                                 "md_standard_uri, mime_type, metadata) VALUES "
    4383             :                                 "('dataset','http://gdal.org','text/xml','%q')",
    4384             :                                 pszXML);
    4385             :         }
    4386         338 :         SQLCommand(hDB, pszSQL);
    4387         338 :         sqlite3_free(pszSQL);
    4388             : 
    4389         338 :         CPLFree(pszXML);
    4390             : 
    4391         338 :         if (mdId < 0)
    4392             :         {
    4393         312 :             const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
    4394         312 :             if (pszTableName != nullptr && pszTableName[0] != '\0')
    4395             :             {
    4396         300 :                 pszSQL = sqlite3_mprintf(
    4397             :                     "INSERT INTO gpkg_metadata_reference (reference_scope, "
    4398             :                     "table_name, timestamp, md_file_id) VALUES "
    4399             :                     "('table', '%q', %s, %d)",
    4400         600 :                     pszTableName, GetCurrentDateEscapedSQL().c_str(),
    4401             :                     static_cast<int>(nFID));
    4402             :             }
    4403             :             else
    4404             :             {
    4405          12 :                 pszSQL = sqlite3_mprintf(
    4406             :                     "INSERT INTO gpkg_metadata_reference (reference_scope, "
    4407             :                     "timestamp, md_file_id) VALUES "
    4408             :                     "('geopackage', %s, %d)",
    4409          24 :                     GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
    4410             :             }
    4411             :         }
    4412             :         else
    4413             :         {
    4414          26 :             pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
    4415             :                                      "timestamp = %s WHERE md_file_id = %d",
    4416          52 :                                      GetCurrentDateEscapedSQL().c_str(), mdId);
    4417             :         }
    4418         338 :         SQLCommand(hDB, pszSQL);
    4419         338 :         sqlite3_free(pszSQL);
    4420             :     }
    4421             : }
    4422             : 
    4423             : /************************************************************************/
    4424             : /*                        CreateMetadataTables()                        */
    4425             : /************************************************************************/
    4426             : 
    4427         309 : bool GDALGeoPackageDataset::CreateMetadataTables()
    4428             : {
    4429             :     const bool bCreateTriggers =
    4430         309 :         CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
    4431             : 
    4432             :     /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL  */
    4433             :     CPLString osSQL = "CREATE TABLE gpkg_metadata ("
    4434             :                       "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
    4435             :                       "md_scope TEXT NOT NULL DEFAULT 'dataset',"
    4436             :                       "md_standard_uri TEXT NOT NULL,"
    4437             :                       "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
    4438             :                       "metadata TEXT NOT NULL DEFAULT ''"
    4439         618 :                       ")";
    4440             : 
    4441             :     /* From D.2. metadata Table 40. metadata Trigger Definition SQL  */
    4442         309 :     const char *pszMetadataTriggers =
    4443             :         "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
    4444             :         "BEFORE INSERT ON 'gpkg_metadata' "
    4445             :         "FOR EACH ROW BEGIN "
    4446             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
    4447             :         "constraint: md_scope must be one of undefined | fieldSession | "
    4448             :         "collectionSession | series | dataset | featureType | feature | "
    4449             :         "attributeType | attribute | tile | model | catalogue | schema | "
    4450             :         "taxonomy software | service | collectionHardware | "
    4451             :         "nonGeographicDataset | dimensionGroup') "
    4452             :         "WHERE NOT(NEW.md_scope IN "
    4453             :         "('undefined','fieldSession','collectionSession','series','dataset', "
    4454             :         "'featureType','feature','attributeType','attribute','tile','model', "
    4455             :         "'catalogue','schema','taxonomy','software','service', "
    4456             :         "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
    4457             :         "END; "
    4458             :         "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
    4459             :         "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
    4460             :         "FOR EACH ROW BEGIN "
    4461             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
    4462             :         "constraint: md_scope must be one of undefined | fieldSession | "
    4463             :         "collectionSession | series | dataset | featureType | feature | "
    4464             :         "attributeType | attribute | tile | model | catalogue | schema | "
    4465             :         "taxonomy software | service | collectionHardware | "
    4466             :         "nonGeographicDataset | dimensionGroup') "
    4467             :         "WHERE NOT(NEW.md_scope IN "
    4468             :         "('undefined','fieldSession','collectionSession','series','dataset', "
    4469             :         "'featureType','feature','attributeType','attribute','tile','model', "
    4470             :         "'catalogue','schema','taxonomy','software','service', "
    4471             :         "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
    4472             :         "END";
    4473         309 :     if (bCreateTriggers)
    4474             :     {
    4475           0 :         osSQL += ";";
    4476           0 :         osSQL += pszMetadataTriggers;
    4477             :     }
    4478             : 
    4479             :     /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
    4480             :      * Table Definition SQL */
    4481             :     osSQL += ";"
    4482             :              "CREATE TABLE gpkg_metadata_reference ("
    4483             :              "reference_scope TEXT NOT NULL,"
    4484             :              "table_name TEXT,"
    4485             :              "column_name TEXT,"
    4486             :              "row_id_value INTEGER,"
    4487             :              "timestamp DATETIME NOT NULL DEFAULT "
    4488             :              "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
    4489             :              "md_file_id INTEGER NOT NULL,"
    4490             :              "md_parent_id INTEGER,"
    4491             :              "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
    4492             :              "gpkg_metadata(id),"
    4493             :              "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
    4494             :              "gpkg_metadata(id)"
    4495         309 :              ")";
    4496             : 
    4497             :     /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
    4498             :      * Definition SQL   */
    4499         309 :     const char *pszMetadataReferenceTriggers =
    4500             :         "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_insert' "
    4501             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4502             :         "FOR EACH ROW BEGIN "
    4503             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4504             :         "violates constraint: reference_scope must be one of \"geopackage\", "
    4505             :         "table\", \"column\", \"row\", \"row/col\"') "
    4506             :         "WHERE NOT NEW.reference_scope IN "
    4507             :         "('geopackage','table','column','row','row/col'); "
    4508             :         "END; "
    4509             :         "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
    4510             :         "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
    4511             :         "FOR EACH ROW BEGIN "
    4512             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4513             :         "violates constraint: reference_scope must be one of \"geopackage\", "
    4514             :         "\"table\", \"column\", \"row\", \"row/col\"') "
    4515             :         "WHERE NOT NEW.reference_scope IN "
    4516             :         "('geopackage','table','column','row','row/col'); "
    4517             :         "END; "
    4518             :         "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
    4519             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4520             :         "FOR EACH ROW BEGIN "
    4521             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4522             :         "violates constraint: column name must be NULL when reference_scope "
    4523             :         "is \"geopackage\", \"table\" or \"row\"') "
    4524             :         "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
    4525             :         "AND NEW.column_name IS NOT NULL); "
    4526             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4527             :         "violates constraint: column name must be defined for the specified "
    4528             :         "table when reference_scope is \"column\" or \"row/col\"') "
    4529             :         "WHERE (NEW.reference_scope IN ('column','row/col') "
    4530             :         "AND NOT NEW.table_name IN ( "
    4531             :         "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
    4532             :         "AND name = NEW.table_name "
    4533             :         "AND sql LIKE ('%' || NEW.column_name || '%'))); "
    4534             :         "END; "
    4535             :         "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
    4536             :         "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
    4537             :         "FOR EACH ROW BEGIN "
    4538             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4539             :         "violates constraint: column name must be NULL when reference_scope "
    4540             :         "is \"geopackage\", \"table\" or \"row\"') "
    4541             :         "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
    4542             :         "AND NEW.column_name IS NOT NULL); "
    4543             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4544             :         "violates constraint: column name must be defined for the specified "
    4545             :         "table when reference_scope is \"column\" or \"row/col\"') "
    4546             :         "WHERE (NEW.reference_scope IN ('column','row/col') "
    4547             :         "AND NOT NEW.table_name IN ( "
    4548             :         "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
    4549             :         "AND name = NEW.table_name "
    4550             :         "AND sql LIKE ('%' || NEW.column_name || '%'))); "
    4551             :         "END; "
    4552             :         "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_insert' "
    4553             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4554             :         "FOR EACH ROW BEGIN "
    4555             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4556             :         "violates constraint: row_id_value must be NULL when reference_scope "
    4557             :         "is \"geopackage\", \"table\" or \"column\"') "
    4558             :         "WHERE NEW.reference_scope IN ('geopackage','table','column') "
    4559             :         "AND NEW.row_id_value IS NOT NULL; "
    4560             :         "END; "
    4561             :         "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
    4562             :         "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
    4563             :         "FOR EACH ROW BEGIN "
    4564             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4565             :         "violates constraint: row_id_value must be NULL when reference_scope "
    4566             :         "is \"geopackage\", \"table\" or \"column\"') "
    4567             :         "WHERE NEW.reference_scope IN ('geopackage','table','column') "
    4568             :         "AND NEW.row_id_value IS NOT NULL; "
    4569             :         "END; "
    4570             :         "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
    4571             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4572             :         "FOR EACH ROW BEGIN "
    4573             :         "SELECT RAISE(ABORT, 'insert 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             :         "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
    4582             :         "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
    4583             :         "FOR EACH ROW BEGIN "
    4584             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4585             :         "violates constraint: timestamp must be a valid time in ISO 8601 "
    4586             :         "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
    4587             :         "WHERE NOT (NEW.timestamp GLOB "
    4588             :         "'[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-"
    4589             :         "5][0-9].[0-9][0-9][0-9]Z' "
    4590             :         "AND strftime('%s',NEW.timestamp) NOT NULL); "
    4591             :         "END";
    4592         309 :     if (bCreateTriggers)
    4593             :     {
    4594           0 :         osSQL += ";";
    4595           0 :         osSQL += pszMetadataReferenceTriggers;
    4596             :     }
    4597             : 
    4598         309 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    4599           2 :         return false;
    4600             : 
    4601         307 :     osSQL += ";";
    4602             :     osSQL += "INSERT INTO gpkg_extensions "
    4603             :              "(table_name, column_name, extension_name, definition, scope) "
    4604             :              "VALUES "
    4605             :              "('gpkg_metadata', NULL, 'gpkg_metadata', "
    4606             :              "'http://www.geopackage.org/spec120/#extension_metadata', "
    4607         307 :              "'read-write')";
    4608             : 
    4609         307 :     osSQL += ";";
    4610             :     osSQL += "INSERT INTO gpkg_extensions "
    4611             :              "(table_name, column_name, extension_name, definition, scope) "
    4612             :              "VALUES "
    4613             :              "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
    4614             :              "'http://www.geopackage.org/spec120/#extension_metadata', "
    4615         307 :              "'read-write')";
    4616             : 
    4617         307 :     const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
    4618         307 :     m_nHasMetadataTables = bOK;
    4619         307 :     return bOK;
    4620             : }
    4621             : 
    4622             : /************************************************************************/
    4623             : /*                           FlushMetadata()                            */
    4624             : /************************************************************************/
    4625             : 
    4626        8934 : void GDALGeoPackageDataset::FlushMetadata()
    4627             : {
    4628        8934 :     if (!m_bMetadataDirty || m_poParentDS != nullptr ||
    4629         368 :         m_nCreateMetadataTables == FALSE)
    4630        8572 :         return;
    4631         362 :     m_bMetadataDirty = false;
    4632             : 
    4633         362 :     if (eAccess == GA_ReadOnly)
    4634             :     {
    4635           3 :         return;
    4636             :     }
    4637             : 
    4638         359 :     bool bCanWriteAreaOrPoint =
    4639         716 :         !m_bGridCellEncodingAsCO &&
    4640         357 :         (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
    4641         359 :     if (!m_osRasterTable.empty())
    4642             :     {
    4643             :         const char *pszIdentifier =
    4644         144 :             GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
    4645             :         const char *pszDescription =
    4646         144 :             GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
    4647         173 :         if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
    4648          29 :             pszIdentifier != m_osIdentifier)
    4649             :         {
    4650          14 :             m_osIdentifier = pszIdentifier;
    4651             :             char *pszSQL =
    4652          14 :                 sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
    4653             :                                 "WHERE lower(table_name) = lower('%q')",
    4654             :                                 pszIdentifier, m_osRasterTable.c_str());
    4655          14 :             SQLCommand(hDB, pszSQL);
    4656          14 :             sqlite3_free(pszSQL);
    4657             :         }
    4658         151 :         if (!m_bDescriptionAsCO && pszDescription != nullptr &&
    4659           7 :             pszDescription != m_osDescription)
    4660             :         {
    4661           7 :             m_osDescription = pszDescription;
    4662             :             char *pszSQL =
    4663           7 :                 sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
    4664             :                                 "WHERE lower(table_name) = lower('%q')",
    4665             :                                 pszDescription, m_osRasterTable.c_str());
    4666           7 :             SQLCommand(hDB, pszSQL);
    4667           7 :             sqlite3_free(pszSQL);
    4668             :         }
    4669         144 :         if (bCanWriteAreaOrPoint)
    4670             :         {
    4671             :             const char *pszAreaOrPoint =
    4672          28 :                 GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
    4673          28 :             if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
    4674             :             {
    4675          23 :                 bCanWriteAreaOrPoint = false;
    4676          23 :                 char *pszSQL = sqlite3_mprintf(
    4677             :                     "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
    4678             :                     "grid_cell_encoding = 'grid-value-is-area' WHERE "
    4679             :                     "lower(tile_matrix_set_name) = lower('%q')",
    4680             :                     m_osRasterTable.c_str());
    4681          23 :                 SQLCommand(hDB, pszSQL);
    4682          23 :                 sqlite3_free(pszSQL);
    4683             :             }
    4684           5 :             else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
    4685             :             {
    4686           1 :                 bCanWriteAreaOrPoint = false;
    4687           1 :                 char *pszSQL = sqlite3_mprintf(
    4688             :                     "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
    4689             :                     "grid_cell_encoding = 'grid-value-is-center' WHERE "
    4690             :                     "lower(tile_matrix_set_name) = lower('%q')",
    4691             :                     m_osRasterTable.c_str());
    4692           1 :                 SQLCommand(hDB, pszSQL);
    4693           1 :                 sqlite3_free(pszSQL);
    4694             :             }
    4695             :         }
    4696             :     }
    4697             : 
    4698         359 :     char **papszMDDup = nullptr;
    4699         205 :     for (const char *pszKeyValue :
    4700         769 :          cpl::Iterate(GDALGeoPackageDataset::GetMetadata()))
    4701             :     {
    4702         205 :         if (STARTS_WITH_CI(pszKeyValue, "IDENTIFIER="))
    4703          29 :             continue;
    4704         176 :         if (STARTS_WITH_CI(pszKeyValue, "DESCRIPTION="))
    4705           8 :             continue;
    4706         168 :         if (STARTS_WITH_CI(pszKeyValue, "ZOOM_LEVEL="))
    4707          14 :             continue;
    4708         154 :         if (STARTS_WITH_CI(pszKeyValue, "GPKG_METADATA_ITEM_"))
    4709           4 :             continue;
    4710         150 :         if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
    4711          29 :             !bCanWriteAreaOrPoint &&
    4712          26 :             STARTS_WITH_CI(pszKeyValue, GDALMD_AREA_OR_POINT))
    4713             :         {
    4714          26 :             continue;
    4715             :         }
    4716         124 :         papszMDDup = CSLInsertString(papszMDDup, -1, pszKeyValue);
    4717             :     }
    4718             : 
    4719         359 :     CPLXMLNode *psXMLNode = nullptr;
    4720             :     {
    4721         359 :         GDALMultiDomainMetadata oLocalMDMD;
    4722         359 :         CSLConstList papszDomainList = oMDMD.GetDomainList();
    4723         359 :         CSLConstList papszIter = papszDomainList;
    4724         359 :         oLocalMDMD.SetMetadata(papszMDDup);
    4725         688 :         while (papszIter && *papszIter)
    4726             :         {
    4727         329 :             if (!EQUAL(*papszIter, "") &&
    4728         159 :                 !EQUAL(*papszIter, "IMAGE_STRUCTURE") &&
    4729          15 :                 !EQUAL(*papszIter, "GEOPACKAGE"))
    4730             :             {
    4731           8 :                 oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
    4732             :                                        *papszIter);
    4733             :             }
    4734         329 :             papszIter++;
    4735             :         }
    4736         359 :         if (m_nBandCountFromMetadata > 0)
    4737             :         {
    4738          74 :             oLocalMDMD.SetMetadataItem(
    4739             :                 "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
    4740             :                 "IMAGE_STRUCTURE");
    4741          74 :             if (nBands == 1)
    4742             :             {
    4743          50 :                 const auto poCT = GetRasterBand(1)->GetColorTable();
    4744          50 :                 if (poCT)
    4745             :                 {
    4746          16 :                     std::string osVal("{");
    4747           8 :                     const int nColorCount = poCT->GetColorEntryCount();
    4748        2056 :                     for (int i = 0; i < nColorCount; ++i)
    4749             :                     {
    4750        2048 :                         if (i > 0)
    4751        2040 :                             osVal += ',';
    4752        2048 :                         const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
    4753             :                         osVal +=
    4754        2048 :                             CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
    4755        2048 :                                        psEntry->c2, psEntry->c3, psEntry->c4);
    4756             :                     }
    4757           8 :                     osVal += '}';
    4758           8 :                     oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
    4759             :                                                "IMAGE_STRUCTURE");
    4760             :                 }
    4761             :             }
    4762          74 :             if (nBands == 1)
    4763             :             {
    4764          50 :                 const char *pszTILE_FORMAT = nullptr;
    4765          50 :                 switch (m_eTF)
    4766             :                 {
    4767           0 :                     case GPKG_TF_PNG_JPEG:
    4768           0 :                         pszTILE_FORMAT = "JPEG_PNG";
    4769           0 :                         break;
    4770          44 :                     case GPKG_TF_PNG:
    4771          44 :                         break;
    4772           0 :                     case GPKG_TF_PNG8:
    4773           0 :                         pszTILE_FORMAT = "PNG8";
    4774           0 :                         break;
    4775           3 :                     case GPKG_TF_JPEG:
    4776           3 :                         pszTILE_FORMAT = "JPEG";
    4777           3 :                         break;
    4778           3 :                     case GPKG_TF_WEBP:
    4779           3 :                         pszTILE_FORMAT = "WEBP";
    4780           3 :                         break;
    4781           0 :                     case GPKG_TF_PNG_16BIT:
    4782           0 :                         break;
    4783           0 :                     case GPKG_TF_TIFF_32BIT_FLOAT:
    4784           0 :                         break;
    4785             :                 }
    4786          50 :                 if (pszTILE_FORMAT)
    4787           6 :                     oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
    4788             :                                                "IMAGE_STRUCTURE");
    4789             :             }
    4790             :         }
    4791         503 :         if (GetRasterCount() > 0 &&
    4792         144 :             GetRasterBand(1)->GetRasterDataType() == GDT_UInt8)
    4793             :         {
    4794         114 :             int bHasNoData = FALSE;
    4795             :             const double dfNoDataValue =
    4796         114 :                 GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    4797         114 :             if (bHasNoData)
    4798             :             {
    4799           3 :                 oLocalMDMD.SetMetadataItem("NODATA_VALUE",
    4800             :                                            CPLSPrintf("%.17g", dfNoDataValue),
    4801             :                                            "IMAGE_STRUCTURE");
    4802             :             }
    4803             :         }
    4804         608 :         for (int i = 1; i <= GetRasterCount(); ++i)
    4805             :         {
    4806             :             auto poBand =
    4807         249 :                 cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
    4808         249 :             poBand->AddImplicitStatistics(false);
    4809         249 :             CSLConstList papszMD = GetRasterBand(i)->GetMetadata();
    4810         249 :             poBand->AddImplicitStatistics(true);
    4811         249 :             if (papszMD)
    4812             :             {
    4813          14 :                 oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
    4814             :             }
    4815             :         }
    4816         359 :         psXMLNode = oLocalMDMD.Serialize();
    4817             :     }
    4818             : 
    4819         359 :     CSLDestroy(papszMDDup);
    4820         359 :     papszMDDup = nullptr;
    4821             : 
    4822         359 :     WriteMetadata(psXMLNode, m_osRasterTable.c_str());
    4823             : 
    4824         359 :     if (!m_osRasterTable.empty())
    4825             :     {
    4826             :         CSLConstList papszGeopackageMD =
    4827         144 :             GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
    4828             : 
    4829         144 :         papszMDDup = nullptr;
    4830         144 :         for (CSLConstList papszIter = papszGeopackageMD;
    4831         153 :              papszIter && *papszIter; ++papszIter)
    4832             :         {
    4833           9 :             papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4834             :         }
    4835             : 
    4836         288 :         GDALMultiDomainMetadata oLocalMDMD;
    4837         144 :         oLocalMDMD.SetMetadata(papszMDDup);
    4838         144 :         CSLDestroy(papszMDDup);
    4839         144 :         papszMDDup = nullptr;
    4840         144 :         psXMLNode = oLocalMDMD.Serialize();
    4841             : 
    4842         144 :         WriteMetadata(psXMLNode, nullptr);
    4843             :     }
    4844             : 
    4845         591 :     for (auto &poLayer : m_apoLayers)
    4846             :     {
    4847         232 :         const char *pszIdentifier = poLayer->GetMetadataItem("IDENTIFIER");
    4848         232 :         const char *pszDescription = poLayer->GetMetadataItem("DESCRIPTION");
    4849         232 :         if (pszIdentifier != nullptr)
    4850             :         {
    4851             :             char *pszSQL =
    4852           3 :                 sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
    4853             :                                 "WHERE lower(table_name) = lower('%q')",
    4854             :                                 pszIdentifier, poLayer->GetName());
    4855           3 :             SQLCommand(hDB, pszSQL);
    4856           3 :             sqlite3_free(pszSQL);
    4857             :         }
    4858         232 :         if (pszDescription != nullptr)
    4859             :         {
    4860             :             char *pszSQL =
    4861           3 :                 sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
    4862             :                                 "WHERE lower(table_name) = lower('%q')",
    4863             :                                 pszDescription, poLayer->GetName());
    4864           3 :             SQLCommand(hDB, pszSQL);
    4865           3 :             sqlite3_free(pszSQL);
    4866             :         }
    4867             : 
    4868         232 :         papszMDDup = nullptr;
    4869         628 :         for (CSLConstList papszIter = poLayer->GetMetadata();
    4870         628 :              papszIter && *papszIter; ++papszIter)
    4871             :         {
    4872         396 :             if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
    4873           3 :                 continue;
    4874         393 :             if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
    4875           3 :                 continue;
    4876         390 :             if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
    4877           0 :                 continue;
    4878         390 :             papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4879             :         }
    4880             : 
    4881             :         {
    4882         232 :             GDALMultiDomainMetadata oLocalMDMD;
    4883         232 :             char **papszDomainList = poLayer->GetMetadataDomainList();
    4884         232 :             char **papszIter = papszDomainList;
    4885         232 :             oLocalMDMD.SetMetadata(papszMDDup);
    4886         517 :             while (papszIter && *papszIter)
    4887             :             {
    4888         285 :                 if (!EQUAL(*papszIter, ""))
    4889          66 :                     oLocalMDMD.SetMetadata(poLayer->GetMetadata(*papszIter),
    4890             :                                            *papszIter);
    4891         285 :                 papszIter++;
    4892             :             }
    4893         232 :             CSLDestroy(papszDomainList);
    4894         232 :             psXMLNode = oLocalMDMD.Serialize();
    4895             :         }
    4896             : 
    4897         232 :         CSLDestroy(papszMDDup);
    4898         232 :         papszMDDup = nullptr;
    4899             : 
    4900         232 :         WriteMetadata(psXMLNode, poLayer->GetName());
    4901             :     }
    4902             : }
    4903             : 
    4904             : /************************************************************************/
    4905             : /*                          GetMetadataItem()                           */
    4906             : /************************************************************************/
    4907             : 
    4908        1927 : const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
    4909             :                                                    const char *pszDomain)
    4910             : {
    4911        1927 :     pszDomain = CheckMetadataDomain(pszDomain);
    4912        1927 :     return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
    4913             : }
    4914             : 
    4915             : /************************************************************************/
    4916             : /*                            SetMetadata()                             */
    4917             : /************************************************************************/
    4918             : 
    4919         133 : CPLErr GDALGeoPackageDataset::SetMetadata(CSLConstList papszMetadata,
    4920             :                                           const char *pszDomain)
    4921             : {
    4922         133 :     pszDomain = CheckMetadataDomain(pszDomain);
    4923         133 :     m_bMetadataDirty = true;
    4924         133 :     GetMetadata(); /* force loading from storage if needed */
    4925         133 :     return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
    4926             : }
    4927             : 
    4928             : /************************************************************************/
    4929             : /*                          SetMetadataItem()                           */
    4930             : /************************************************************************/
    4931             : 
    4932          21 : CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
    4933             :                                               const char *pszValue,
    4934             :                                               const char *pszDomain)
    4935             : {
    4936          21 :     pszDomain = CheckMetadataDomain(pszDomain);
    4937          21 :     m_bMetadataDirty = true;
    4938          21 :     GetMetadata(); /* force loading from storage if needed */
    4939          21 :     return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    4940             : }
    4941             : 
    4942             : /************************************************************************/
    4943             : /*                               Create()                               */
    4944             : /************************************************************************/
    4945             : 
    4946        1020 : int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
    4947             :                                   int nYSize, int nBandsIn, GDALDataType eDT,
    4948             :                                   CSLConstList papszOptions)
    4949             : {
    4950        2040 :     CPLString osCommand;
    4951             : 
    4952             :     /* First, ensure there isn't any such file yet. */
    4953             :     VSIStatBufL sStatBuf;
    4954             : 
    4955        1020 :     if (nBandsIn != 0)
    4956             :     {
    4957         226 :         if (eDT == GDT_UInt8)
    4958             :         {
    4959         156 :             if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
    4960             :                 nBandsIn != 4)
    4961             :             {
    4962           1 :                 CPLError(CE_Failure, CPLE_NotSupported,
    4963             :                          "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
    4964             :                          "3 (RGB) or 4 (RGBA) band dataset supported for "
    4965             :                          "Byte datatype");
    4966           1 :                 return FALSE;
    4967             :             }
    4968             :         }
    4969          70 :         else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
    4970             :         {
    4971          43 :             if (nBandsIn != 1)
    4972             :             {
    4973           3 :                 CPLError(CE_Failure, CPLE_NotSupported,
    4974             :                          "Only single band dataset supported for non Byte "
    4975             :                          "datatype");
    4976           3 :                 return FALSE;
    4977             :             }
    4978             :         }
    4979             :         else
    4980             :         {
    4981          27 :             CPLError(CE_Failure, CPLE_NotSupported,
    4982             :                      "Only Byte, Int16, UInt16 or Float32 supported");
    4983          27 :             return FALSE;
    4984             :         }
    4985             :     }
    4986             : 
    4987         989 :     const size_t nFilenameLen = strlen(pszFilename);
    4988         989 :     const bool bGpkgZip =
    4989         984 :         (nFilenameLen > strlen(".gpkg.zip") &&
    4990        1973 :          !STARTS_WITH(pszFilename, "/vsizip/") &&
    4991         984 :          EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
    4992             : 
    4993             :     const bool bUseTempFile =
    4994         990 :         bGpkgZip || (CPLTestBool(CPLGetConfigOption(
    4995           1 :                          "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
    4996           1 :                      (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
    4997           1 :                       EQUAL(CPLGetConfigOption(
    4998             :                                 "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
    4999         989 :                             "FORCED")));
    5000             : 
    5001         989 :     bool bFileExists = false;
    5002         989 :     if (VSIStatL(pszFilename, &sStatBuf) == 0)
    5003             :     {
    5004          10 :         bFileExists = true;
    5005          20 :         if (nBandsIn == 0 || bUseTempFile ||
    5006          10 :             !CPLTestBool(
    5007             :                 CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
    5008             :         {
    5009           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5010             :                      "A file system object called '%s' already exists.",
    5011             :                      pszFilename);
    5012             : 
    5013           0 :             return FALSE;
    5014             :         }
    5015             :     }
    5016             : 
    5017         989 :     if (bUseTempFile)
    5018             :     {
    5019           3 :         if (bGpkgZip)
    5020             :         {
    5021           2 :             std::string osFilenameInZip(CPLGetFilename(pszFilename));
    5022           2 :             osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
    5023             :             m_osFinalFilename =
    5024           2 :                 std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
    5025             :         }
    5026             :         else
    5027             :         {
    5028           1 :             m_osFinalFilename = pszFilename;
    5029             :         }
    5030           3 :         m_pszFilename = CPLStrdup(
    5031           6 :             CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename)).c_str());
    5032           3 :         CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
    5033             :     }
    5034             :     else
    5035             :     {
    5036         986 :         m_pszFilename = CPLStrdup(pszFilename);
    5037             :     }
    5038         989 :     m_bNew = true;
    5039         989 :     eAccess = GA_Update;
    5040         989 :     m_bDateTimeWithTZ =
    5041         989 :         EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
    5042             :               "WITH_TZ");
    5043             : 
    5044             :     // for test/debug purposes only. true is the nominal value
    5045         989 :     m_bPNGSupports2Bands =
    5046         989 :         CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
    5047         989 :     m_bPNGSupportsCT =
    5048         989 :         CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
    5049             : 
    5050         989 :     if (!OpenOrCreateDB(bFileExists
    5051             :                             ? SQLITE_OPEN_READWRITE
    5052             :                             : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
    5053           8 :         return FALSE;
    5054             : 
    5055             :     /* Default to synchronous=off for performance for new file */
    5056        1952 :     if (!bFileExists &&
    5057         971 :         CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
    5058             :     {
    5059         457 :         SQLCommand(hDB, "PRAGMA synchronous = OFF");
    5060             :     }
    5061             : 
    5062             :     /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
    5063             :     /* will be written into the main file and supported henceforth */
    5064         981 :     SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
    5065             : 
    5066         981 :     if (bFileExists)
    5067             :     {
    5068          10 :         VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
    5069          10 :         if (fp)
    5070             :         {
    5071             :             GByte abyHeader[100];
    5072          10 :             VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
    5073          10 :             VSIFCloseL(fp);
    5074             : 
    5075          10 :             memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
    5076          10 :             m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
    5077          10 :             memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
    5078          10 :             m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
    5079             : 
    5080          10 :             if (m_nApplicationId == GP10_APPLICATION_ID)
    5081             :             {
    5082           0 :                 CPLDebug("GPKG", "GeoPackage v1.0");
    5083             :             }
    5084          10 :             else if (m_nApplicationId == GP11_APPLICATION_ID)
    5085             :             {
    5086           0 :                 CPLDebug("GPKG", "GeoPackage v1.1");
    5087             :             }
    5088          10 :             else if (m_nApplicationId == GPKG_APPLICATION_ID &&
    5089          10 :                      m_nUserVersion >= GPKG_1_2_VERSION)
    5090             :             {
    5091          10 :                 CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
    5092          10 :                          (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
    5093             :             }
    5094             :         }
    5095             : 
    5096          10 :         DetectSpatialRefSysColumns();
    5097             :     }
    5098             : 
    5099         981 :     const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
    5100         981 :     if (pszVersion && !EQUAL(pszVersion, "AUTO"))
    5101             :     {
    5102          40 :         if (EQUAL(pszVersion, "1.0"))
    5103             :         {
    5104           2 :             m_nApplicationId = GP10_APPLICATION_ID;
    5105           2 :             m_nUserVersion = 0;
    5106             :         }
    5107          38 :         else if (EQUAL(pszVersion, "1.1"))
    5108             :         {
    5109           1 :             m_nApplicationId = GP11_APPLICATION_ID;
    5110           1 :             m_nUserVersion = 0;
    5111             :         }
    5112          37 :         else if (EQUAL(pszVersion, "1.2"))
    5113             :         {
    5114          15 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5115          15 :             m_nUserVersion = GPKG_1_2_VERSION;
    5116             :         }
    5117          22 :         else if (EQUAL(pszVersion, "1.3"))
    5118             :         {
    5119           3 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5120           3 :             m_nUserVersion = GPKG_1_3_VERSION;
    5121             :         }
    5122          19 :         else if (EQUAL(pszVersion, "1.4"))
    5123             :         {
    5124          19 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5125          19 :             m_nUserVersion = GPKG_1_4_VERSION;
    5126             :         }
    5127             :     }
    5128             : 
    5129         981 :     SoftStartTransaction();
    5130             : 
    5131        1962 :     CPLString osSQL;
    5132         981 :     if (!bFileExists)
    5133             :     {
    5134             :         /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
    5135             :          * table */
    5136             :         /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5137             :         osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
    5138             :                 "srs_name TEXT NOT NULL,"
    5139             :                 "srs_id INTEGER NOT NULL PRIMARY KEY,"
    5140             :                 "organization TEXT NOT NULL,"
    5141             :                 "organization_coordsys_id INTEGER NOT NULL,"
    5142             :                 "definition  TEXT NOT NULL,"
    5143         971 :                 "description TEXT";
    5144         971 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
    5145        1152 :                                              "NO")) ||
    5146         181 :             (nBandsIn != 0 && eDT != GDT_UInt8))
    5147             :         {
    5148          42 :             m_bHasDefinition12_063 = true;
    5149          42 :             osSQL += ", definition_12_063 TEXT NOT NULL";
    5150          42 :             if (m_nUserVersion >= GPKG_1_4_VERSION)
    5151             :             {
    5152          40 :                 osSQL += ", epoch DOUBLE";
    5153          40 :                 m_bHasEpochColumn = true;
    5154             :             }
    5155             :         }
    5156             :         osSQL += ")"
    5157             :                  ";"
    5158             :                  /* Requirement 11: The gpkg_spatial_ref_sys table in a
    5159             :                     GeoPackage SHALL */
    5160             :                  /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
    5161             :                  /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5162             : 
    5163             :                  "INSERT INTO gpkg_spatial_ref_sys ("
    5164             :                  "srs_name, srs_id, organization, organization_coordsys_id, "
    5165         971 :                  "definition, description";
    5166         971 :         if (m_bHasDefinition12_063)
    5167          42 :             osSQL += ", definition_12_063";
    5168             :         osSQL +=
    5169             :             ") VALUES ("
    5170             :             "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
    5171             :             "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
    5172             :             "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
    5173             :             "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
    5174             :             "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
    5175             :             "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
    5176             :             "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
    5177             :             "', 'longitude/latitude coordinates in decimal degrees on the WGS "
    5178         971 :             "84 spheroid'";
    5179         971 :         if (m_bHasDefinition12_063)
    5180             :             osSQL +=
    5181             :                 ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
    5182             :                 "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
    5183             :                 "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
    5184             :                 "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
    5185             :                 "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
    5186             :                 "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
    5187          42 :                 "ID[\"EPSG\", 4326]]'";
    5188             :         osSQL +=
    5189             :             ")"
    5190             :             ";"
    5191             :             /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
    5192             :                SHALL */
    5193             :             /* contain a record with an srs_id of -1, an organization of “NONE”,
    5194             :              */
    5195             :             /* an organization_coordsys_id of -1, and definition “undefined” */
    5196             :             /* for undefined Cartesian coordinate reference systems */
    5197             :             /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5198             :             "INSERT INTO gpkg_spatial_ref_sys ("
    5199             :             "srs_name, srs_id, organization, organization_coordsys_id, "
    5200         971 :             "definition, description";
    5201         971 :         if (m_bHasDefinition12_063)
    5202          42 :             osSQL += ", definition_12_063";
    5203             :         osSQL += ") VALUES ("
    5204             :                  "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
    5205         971 :                  "'undefined Cartesian coordinate reference system'";
    5206         971 :         if (m_bHasDefinition12_063)
    5207          42 :             osSQL += ", 'undefined'";
    5208             :         osSQL +=
    5209             :             ")"
    5210             :             ";"
    5211             :             /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
    5212             :                SHALL */
    5213             :             /* contain a record with an srs_id of 0, an organization of “NONE”,
    5214             :              */
    5215             :             /* an organization_coordsys_id of 0, and definition “undefined” */
    5216             :             /* for undefined geographic coordinate reference systems */
    5217             :             /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5218             :             "INSERT INTO gpkg_spatial_ref_sys ("
    5219             :             "srs_name, srs_id, organization, organization_coordsys_id, "
    5220         971 :             "definition, description";
    5221         971 :         if (m_bHasDefinition12_063)
    5222          42 :             osSQL += ", definition_12_063";
    5223             :         osSQL += ") VALUES ("
    5224             :                  "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
    5225         971 :                  "'undefined geographic coordinate reference system'";
    5226         971 :         if (m_bHasDefinition12_063)
    5227          42 :             osSQL += ", 'undefined'";
    5228             :         osSQL += ")"
    5229             :                  ";"
    5230             :                  /* Requirement 13: A GeoPackage file SHALL include a
    5231             :                     gpkg_contents table */
    5232             :                  /* http://opengis.github.io/geopackage/#_contents */
    5233             :                  "CREATE TABLE gpkg_contents ("
    5234             :                  "table_name TEXT NOT NULL PRIMARY KEY,"
    5235             :                  "data_type TEXT NOT NULL,"
    5236             :                  "identifier TEXT UNIQUE,"
    5237             :                  "description TEXT DEFAULT '',"
    5238             :                  "last_change DATETIME NOT NULL DEFAULT "
    5239             :                  "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
    5240             :                  "min_x DOUBLE, min_y DOUBLE,"
    5241             :                  "max_x DOUBLE, max_y DOUBLE,"
    5242             :                  "srs_id INTEGER,"
    5243             :                  "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
    5244             :                  "gpkg_spatial_ref_sys(srs_id)"
    5245         971 :                  ")";
    5246             : 
    5247             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    5248         971 :         if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
    5249             :         {
    5250         966 :             m_bHasGPKGOGRContents = true;
    5251             :             osSQL += ";"
    5252             :                      "CREATE TABLE gpkg_ogr_contents("
    5253             :                      "table_name TEXT NOT NULL PRIMARY KEY,"
    5254             :                      "feature_count INTEGER DEFAULT NULL"
    5255         966 :                      ")";
    5256             :         }
    5257             : #endif
    5258             : 
    5259             :         /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
    5260             :          * “features” */
    5261             :         /* data_type SHALL contain a gpkg_geometry_columns table or updateable
    5262             :          * view */
    5263             :         /* http://opengis.github.io/geopackage/#_geometry_columns */
    5264             :         const bool bCreateGeometryColumns =
    5265         971 :             CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
    5266         971 :         if (bCreateGeometryColumns)
    5267             :         {
    5268         970 :             m_bHasGPKGGeometryColumns = true;
    5269         970 :             osSQL += ";";
    5270         970 :             osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
    5271             :         }
    5272             :     }
    5273             : 
    5274             :     const bool bCreateTriggers =
    5275         981 :         CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
    5276          10 :     if ((bFileExists && nBandsIn != 0 &&
    5277          10 :          SQLGetInteger(
    5278             :              hDB,
    5279             :              "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
    5280             :              "AND type in ('table', 'view')",
    5281        1962 :              nullptr) == 0) ||
    5282         980 :         (!bFileExists &&
    5283         971 :          CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
    5284             :     {
    5285         971 :         if (!osSQL.empty())
    5286         970 :             osSQL += ";";
    5287             : 
    5288             :         /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
    5289             :          * Creation SQL  */
    5290             :         osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
    5291             :                  "table_name TEXT NOT NULL PRIMARY KEY,"
    5292             :                  "srs_id INTEGER NOT NULL,"
    5293             :                  "min_x DOUBLE NOT NULL,"
    5294             :                  "min_y DOUBLE NOT NULL,"
    5295             :                  "max_x DOUBLE NOT NULL,"
    5296             :                  "max_y DOUBLE NOT NULL,"
    5297             :                  "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
    5298             :                  "REFERENCES gpkg_contents(table_name),"
    5299             :                  "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
    5300             :                  "gpkg_spatial_ref_sys (srs_id)"
    5301             :                  ")"
    5302             :                  ";"
    5303             : 
    5304             :                  /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
    5305             :                     Creation SQL */
    5306             :                  "CREATE TABLE gpkg_tile_matrix ("
    5307             :                  "table_name TEXT NOT NULL,"
    5308             :                  "zoom_level INTEGER NOT NULL,"
    5309             :                  "matrix_width INTEGER NOT NULL,"
    5310             :                  "matrix_height INTEGER NOT NULL,"
    5311             :                  "tile_width INTEGER NOT NULL,"
    5312             :                  "tile_height INTEGER NOT NULL,"
    5313             :                  "pixel_x_size DOUBLE NOT NULL,"
    5314             :                  "pixel_y_size DOUBLE NOT NULL,"
    5315             :                  "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
    5316             :                  "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
    5317             :                  "REFERENCES gpkg_contents(table_name)"
    5318         971 :                  ")";
    5319             : 
    5320         971 :         if (bCreateTriggers)
    5321             :         {
    5322             :             /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
    5323             :              * Definition SQL */
    5324         971 :             const char *pszTileMatrixTrigger =
    5325             :                 "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
    5326             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5327             :                 "FOR EACH ROW BEGIN "
    5328             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5329             :                 "violates constraint: zoom_level cannot be less than 0') "
    5330             :                 "WHERE (NEW.zoom_level < 0); "
    5331             :                 "END; "
    5332             :                 "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
    5333             :                 "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
    5334             :                 "FOR EACH ROW BEGIN "
    5335             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5336             :                 "violates constraint: zoom_level cannot be less than 0') "
    5337             :                 "WHERE (NEW.zoom_level < 0); "
    5338             :                 "END; "
    5339             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
    5340             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5341             :                 "FOR EACH ROW BEGIN "
    5342             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5343             :                 "violates constraint: matrix_width cannot be less than 1') "
    5344             :                 "WHERE (NEW.matrix_width < 1); "
    5345             :                 "END; "
    5346             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
    5347             :                 "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
    5348             :                 "FOR EACH ROW BEGIN "
    5349             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5350             :                 "violates constraint: matrix_width cannot be less than 1') "
    5351             :                 "WHERE (NEW.matrix_width < 1); "
    5352             :                 "END; "
    5353             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
    5354             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5355             :                 "FOR EACH ROW BEGIN "
    5356             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5357             :                 "violates constraint: matrix_height cannot be less than 1') "
    5358             :                 "WHERE (NEW.matrix_height < 1); "
    5359             :                 "END; "
    5360             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
    5361             :                 "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
    5362             :                 "FOR EACH ROW BEGIN "
    5363             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5364             :                 "violates constraint: matrix_height cannot be less than 1') "
    5365             :                 "WHERE (NEW.matrix_height < 1); "
    5366             :                 "END; "
    5367             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
    5368             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5369             :                 "FOR EACH ROW BEGIN "
    5370             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5371             :                 "violates constraint: pixel_x_size must be greater than 0') "
    5372             :                 "WHERE NOT (NEW.pixel_x_size > 0); "
    5373             :                 "END; "
    5374             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
    5375             :                 "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
    5376             :                 "FOR EACH ROW BEGIN "
    5377             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5378             :                 "violates constraint: pixel_x_size must be greater than 0') "
    5379             :                 "WHERE NOT (NEW.pixel_x_size > 0); "
    5380             :                 "END; "
    5381             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
    5382             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5383             :                 "FOR EACH ROW BEGIN "
    5384             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5385             :                 "violates constraint: pixel_y_size must be greater than 0') "
    5386             :                 "WHERE NOT (NEW.pixel_y_size > 0); "
    5387             :                 "END; "
    5388             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
    5389             :                 "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
    5390             :                 "FOR EACH ROW BEGIN "
    5391             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5392             :                 "violates constraint: pixel_y_size must be greater than 0') "
    5393             :                 "WHERE NOT (NEW.pixel_y_size > 0); "
    5394             :                 "END;";
    5395         971 :             osSQL += ";";
    5396         971 :             osSQL += pszTileMatrixTrigger;
    5397             :         }
    5398             :     }
    5399             : 
    5400         981 :     if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
    5401           1 :         return FALSE;
    5402             : 
    5403         980 :     if (!bFileExists)
    5404             :     {
    5405             :         const char *pszMetadataTables =
    5406         970 :             CSLFetchNameValue(papszOptions, "METADATA_TABLES");
    5407         970 :         if (pszMetadataTables)
    5408          10 :             m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
    5409             : 
    5410         970 :         if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
    5411           0 :             return FALSE;
    5412             : 
    5413         970 :         if (m_bHasDefinition12_063)
    5414             :         {
    5415          84 :             if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
    5416             :                 OGRERR_NONE !=
    5417          42 :                     SQLCommand(hDB, "INSERT INTO gpkg_extensions "
    5418             :                                     "(table_name, column_name, extension_name, "
    5419             :                                     "definition, scope) "
    5420             :                                     "VALUES "
    5421             :                                     "('gpkg_spatial_ref_sys', "
    5422             :                                     "'definition_12_063', 'gpkg_crs_wkt', "
    5423             :                                     "'http://www.geopackage.org/spec120/"
    5424             :                                     "#extension_crs_wkt', 'read-write')"))
    5425             :             {
    5426           0 :                 return FALSE;
    5427             :             }
    5428          42 :             if (m_bHasEpochColumn)
    5429             :             {
    5430          40 :                 if (OGRERR_NONE !=
    5431          40 :                         SQLCommand(
    5432             :                             hDB, "UPDATE gpkg_extensions SET extension_name = "
    5433             :                                  "'gpkg_crs_wkt_1_1' "
    5434          80 :                                  "WHERE extension_name = 'gpkg_crs_wkt'") ||
    5435             :                     OGRERR_NONE !=
    5436          40 :                         SQLCommand(hDB, "INSERT INTO gpkg_extensions "
    5437             :                                         "(table_name, column_name, "
    5438             :                                         "extension_name, definition, scope) "
    5439             :                                         "VALUES "
    5440             :                                         "('gpkg_spatial_ref_sys', 'epoch', "
    5441             :                                         "'gpkg_crs_wkt_1_1', "
    5442             :                                         "'http://www.geopackage.org/spec/"
    5443             :                                         "#extension_crs_wkt', "
    5444             :                                         "'read-write')"))
    5445             :                 {
    5446           0 :                     return FALSE;
    5447             :                 }
    5448             :             }
    5449             :         }
    5450             :     }
    5451             : 
    5452         980 :     if (nBandsIn != 0)
    5453             :     {
    5454         190 :         const std::string osTableName = CPLGetBasenameSafe(m_pszFilename);
    5455             :         m_osRasterTable = CSLFetchNameValueDef(papszOptions, "RASTER_TABLE",
    5456         190 :                                                osTableName.c_str());
    5457         190 :         if (m_osRasterTable.empty())
    5458             :         {
    5459           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5460             :                      "RASTER_TABLE must be set to a non empty value");
    5461           0 :             return FALSE;
    5462             :         }
    5463         190 :         m_bIdentifierAsCO =
    5464         190 :             CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
    5465             :         m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
    5466         190 :                                               m_osRasterTable);
    5467         190 :         m_bDescriptionAsCO =
    5468         190 :             CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
    5469             :         m_osDescription =
    5470         190 :             CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
    5471         190 :         SetDataType(eDT);
    5472         190 :         if (eDT == GDT_Int16)
    5473          16 :             SetGlobalOffsetScale(-32768.0, 1.0);
    5474             : 
    5475             :         /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
    5476             :          * table Create Table SQL (Informative) */
    5477             :         char *pszSQL =
    5478         190 :             sqlite3_mprintf("CREATE TABLE \"%w\" ("
    5479             :                             "id INTEGER PRIMARY KEY AUTOINCREMENT,"
    5480             :                             "zoom_level INTEGER NOT NULL,"
    5481             :                             "tile_column INTEGER NOT NULL,"
    5482             :                             "tile_row INTEGER NOT NULL,"
    5483             :                             "tile_data BLOB NOT NULL,"
    5484             :                             "UNIQUE (zoom_level, tile_column, tile_row)"
    5485             :                             ")",
    5486             :                             m_osRasterTable.c_str());
    5487         190 :         osSQL = pszSQL;
    5488         190 :         sqlite3_free(pszSQL);
    5489             : 
    5490         190 :         if (bCreateTriggers)
    5491             :         {
    5492         190 :             osSQL += ";";
    5493         190 :             osSQL += CreateRasterTriggersSQL(m_osRasterTable);
    5494             :         }
    5495             : 
    5496         190 :         OGRErr eErr = SQLCommand(hDB, osSQL);
    5497         190 :         if (OGRERR_NONE != eErr)
    5498           0 :             return FALSE;
    5499             : 
    5500         190 :         const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
    5501         190 :         if (eDT == GDT_Int16 || eDT == GDT_UInt16)
    5502             :         {
    5503          27 :             m_eTF = GPKG_TF_PNG_16BIT;
    5504          27 :             if (pszTF)
    5505             :             {
    5506           1 :                 if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
    5507             :                 {
    5508           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    5509             :                              "Only AUTO or PNG supported "
    5510             :                              "as tile format for Int16 / UInt16");
    5511             :                 }
    5512             :             }
    5513             :         }
    5514         163 :         else if (eDT == GDT_Float32)
    5515             :         {
    5516          13 :             m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
    5517          13 :             if (pszTF)
    5518             :             {
    5519           5 :                 if (EQUAL(pszTF, "PNG"))
    5520           5 :                     m_eTF = GPKG_TF_PNG_16BIT;
    5521           0 :                 else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
    5522             :                 {
    5523           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    5524             :                              "Only AUTO, PNG or TIFF supported "
    5525             :                              "as tile format for Float32");
    5526             :                 }
    5527             :             }
    5528             :         }
    5529             :         else
    5530             :         {
    5531         150 :             if (pszTF)
    5532             :             {
    5533          71 :                 m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
    5534          71 :                 if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
    5535           7 :                     m_bMetadataDirty = true;
    5536             :             }
    5537          79 :             else if (nBandsIn == 1)
    5538          68 :                 m_eTF = GPKG_TF_PNG;
    5539             :         }
    5540             : 
    5541         190 :         if (eDT != GDT_UInt8)
    5542             :         {
    5543          40 :             if (!CreateTileGriddedTable(papszOptions))
    5544           0 :                 return FALSE;
    5545             :         }
    5546             : 
    5547         190 :         nRasterXSize = nXSize;
    5548         190 :         nRasterYSize = nYSize;
    5549             : 
    5550             :         const char *pszTileSize =
    5551         190 :             CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
    5552             :         const char *pszTileWidth =
    5553         190 :             CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
    5554             :         const char *pszTileHeight =
    5555         190 :             CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
    5556         190 :         int nTileWidth = atoi(pszTileWidth);
    5557         190 :         int nTileHeight = atoi(pszTileHeight);
    5558         190 :         if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
    5559         380 :              nTileHeight > 4096) &&
    5560           1 :             !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
    5561             :         {
    5562           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5563             :                      "Invalid block dimensions: %dx%d", nTileWidth,
    5564             :                      nTileHeight);
    5565           0 :             return FALSE;
    5566             :         }
    5567             : 
    5568         513 :         for (int i = 1; i <= nBandsIn; i++)
    5569             :         {
    5570         323 :             SetBand(i, std::make_unique<GDALGeoPackageRasterBand>(
    5571             :                            this, nTileWidth, nTileHeight));
    5572             :         }
    5573             : 
    5574         190 :         GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
    5575             :                                         "IMAGE_STRUCTURE");
    5576         190 :         GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
    5577         190 :         if (!m_osDescription.empty())
    5578           1 :             GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
    5579             : 
    5580         190 :         ParseCompressionOptions(papszOptions);
    5581             : 
    5582         190 :         if (m_eTF == GPKG_TF_WEBP)
    5583             :         {
    5584          10 :             if (!RegisterWebPExtension())
    5585           0 :                 return FALSE;
    5586             :         }
    5587             : 
    5588             :         m_osTilingScheme =
    5589         190 :             CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
    5590         190 :         if (!EQUAL(m_osTilingScheme, "CUSTOM"))
    5591             :         {
    5592          22 :             const auto poTS = GetTilingScheme(m_osTilingScheme);
    5593          22 :             if (!poTS)
    5594           0 :                 return FALSE;
    5595             : 
    5596          43 :             if (nTileWidth != poTS->nTileWidth ||
    5597          21 :                 nTileHeight != poTS->nTileHeight)
    5598             :             {
    5599           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    5600             :                          "Tile dimension should be %dx%d for %s tiling scheme",
    5601           1 :                          poTS->nTileWidth, poTS->nTileHeight,
    5602             :                          m_osTilingScheme.c_str());
    5603           1 :                 return FALSE;
    5604             :             }
    5605             : 
    5606             :             const char *pszZoomLevel =
    5607          21 :                 CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
    5608          21 :             if (pszZoomLevel)
    5609             :             {
    5610           1 :                 m_nZoomLevel = atoi(pszZoomLevel);
    5611           1 :                 int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
    5612           1 :                 while ((1 << nMaxZoomLevelForThisTM) >
    5613           2 :                            INT_MAX / poTS->nTileXCountZoomLevel0 ||
    5614           1 :                        (1 << nMaxZoomLevelForThisTM) >
    5615           1 :                            INT_MAX / poTS->nTileYCountZoomLevel0)
    5616             :                 {
    5617           0 :                     --nMaxZoomLevelForThisTM;
    5618             :                 }
    5619             : 
    5620           1 :                 if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
    5621             :                 {
    5622           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    5623             :                              "ZOOM_LEVEL = %s is invalid. It should be in "
    5624             :                              "[0,%d] range",
    5625             :                              pszZoomLevel, nMaxZoomLevelForThisTM);
    5626           0 :                     return FALSE;
    5627             :                 }
    5628             :             }
    5629             : 
    5630             :             // Implicitly sets SRS.
    5631          21 :             OGRSpatialReference oSRS;
    5632          21 :             if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
    5633           0 :                 return FALSE;
    5634          21 :             char *pszWKT = nullptr;
    5635          21 :             oSRS.exportToWkt(&pszWKT);
    5636          21 :             SetProjection(pszWKT);
    5637          21 :             CPLFree(pszWKT);
    5638             :         }
    5639             :         else
    5640             :         {
    5641         168 :             if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
    5642             :             {
    5643           0 :                 CPLError(
    5644             :                     CE_Failure, CPLE_NotSupported,
    5645             :                     "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
    5646           0 :                 return false;
    5647             :             }
    5648             :         }
    5649             :     }
    5650             : 
    5651         979 :     if (bFileExists && nBandsIn > 0 && eDT == GDT_UInt8)
    5652             :     {
    5653             :         // If there was an ogr_empty_table table, we can remove it
    5654           9 :         RemoveOGREmptyTable();
    5655             :     }
    5656             : 
    5657         979 :     SoftCommitTransaction();
    5658             : 
    5659             :     /* Requirement 2 */
    5660             :     /* We have to do this after there's some content so the database file */
    5661             :     /* is not zero length */
    5662         979 :     SetApplicationAndUserVersionId();
    5663             : 
    5664             :     /* Default to synchronous=off for performance for new file */
    5665        1948 :     if (!bFileExists &&
    5666         969 :         CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
    5667             :     {
    5668         457 :         SQLCommand(hDB, "PRAGMA synchronous = OFF");
    5669             :     }
    5670             : 
    5671         979 :     return TRUE;
    5672             : }
    5673             : 
    5674             : /************************************************************************/
    5675             : /*                        RemoveOGREmptyTable()                         */
    5676             : /************************************************************************/
    5677             : 
    5678         790 : void GDALGeoPackageDataset::RemoveOGREmptyTable()
    5679             : {
    5680             :     // Run with sqlite3_exec since we don't want errors to be emitted
    5681         790 :     sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
    5682             :                  nullptr);
    5683         790 :     sqlite3_exec(
    5684             :         hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
    5685             :         nullptr, nullptr, nullptr);
    5686             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    5687         790 :     if (m_bHasGPKGOGRContents)
    5688             :     {
    5689         776 :         sqlite3_exec(hDB,
    5690             :                      "DELETE FROM gpkg_ogr_contents WHERE "
    5691             :                      "table_name = 'ogr_empty_table'",
    5692             :                      nullptr, nullptr, nullptr);
    5693             :     }
    5694             : #endif
    5695         790 :     sqlite3_exec(hDB,
    5696             :                  "DELETE FROM gpkg_geometry_columns WHERE "
    5697             :                  "table_name = 'ogr_empty_table'",
    5698             :                  nullptr, nullptr, nullptr);
    5699         790 : }
    5700             : 
    5701             : /************************************************************************/
    5702             : /*                       CreateTileGriddedTable()                       */
    5703             : /************************************************************************/
    5704             : 
    5705          40 : bool GDALGeoPackageDataset::CreateTileGriddedTable(CSLConstList papszOptions)
    5706             : {
    5707          80 :     CPLString osSQL;
    5708          40 :     if (!HasGriddedCoverageAncillaryTable())
    5709             :     {
    5710             :         // It doesn't exist. So create gpkg_extensions table if necessary, and
    5711             :         // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
    5712             :         // and register them as extensions.
    5713          40 :         if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    5714           0 :             return false;
    5715             : 
    5716             :         // Req 1 /table-defs/coverage-ancillary
    5717             :         osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
    5718             :                 "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
    5719             :                 "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
    5720             :                 "datatype TEXT NOT NULL DEFAULT 'integer',"
    5721             :                 "scale REAL NOT NULL DEFAULT 1.0,"
    5722             :                 "offset REAL NOT NULL DEFAULT 0.0,"
    5723             :                 "precision REAL DEFAULT 1.0,"
    5724             :                 "data_null REAL,"
    5725             :                 "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
    5726             :                 "uom TEXT,"
    5727             :                 "field_name TEXT DEFAULT 'Height',"
    5728             :                 "quantity_definition TEXT DEFAULT 'Height',"
    5729             :                 "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
    5730             :                 "REFERENCES gpkg_tile_matrix_set ( table_name ) "
    5731             :                 "CHECK (datatype in ('integer','float')))"
    5732             :                 ";"
    5733             :                 // Requirement 2 /table-defs/tile-ancillary
    5734             :                 "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
    5735             :                 "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
    5736             :                 "tpudt_name TEXT NOT NULL,"
    5737             :                 "tpudt_id INTEGER NOT NULL,"
    5738             :                 "scale REAL NOT NULL DEFAULT 1.0,"
    5739             :                 "offset REAL NOT NULL DEFAULT 0.0,"
    5740             :                 "min REAL DEFAULT NULL,"
    5741             :                 "max REAL DEFAULT NULL,"
    5742             :                 "mean REAL DEFAULT NULL,"
    5743             :                 "std_dev REAL DEFAULT NULL,"
    5744             :                 "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
    5745             :                 "REFERENCES gpkg_contents(table_name),"
    5746             :                 "UNIQUE (tpudt_name, tpudt_id))"
    5747             :                 ";"
    5748             :                 // Requirement 6 /gpkg-extensions
    5749             :                 "INSERT INTO gpkg_extensions "
    5750             :                 "(table_name, column_name, extension_name, definition, scope) "
    5751             :                 "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
    5752             :                 "'gpkg_2d_gridded_coverage', "
    5753             :                 "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5754             :                 "'read-write')"
    5755             :                 ";"
    5756             :                 // Requirement 6 /gpkg-extensions
    5757             :                 "INSERT INTO gpkg_extensions "
    5758             :                 "(table_name, column_name, extension_name, definition, scope) "
    5759             :                 "VALUES ('gpkg_2d_gridded_tile_ancillary', NULL, "
    5760             :                 "'gpkg_2d_gridded_coverage', "
    5761             :                 "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5762             :                 "'read-write')"
    5763          40 :                 ";";
    5764             :     }
    5765             : 
    5766             :     // Requirement 6 /gpkg-extensions
    5767          40 :     char *pszSQL = sqlite3_mprintf(
    5768             :         "INSERT INTO gpkg_extensions "
    5769             :         "(table_name, column_name, extension_name, definition, scope) "
    5770             :         "VALUES ('%q', 'tile_data', "
    5771             :         "'gpkg_2d_gridded_coverage', "
    5772             :         "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5773             :         "'read-write')",
    5774             :         m_osRasterTable.c_str());
    5775          40 :     osSQL += pszSQL;
    5776          40 :     osSQL += ";";
    5777          40 :     sqlite3_free(pszSQL);
    5778             : 
    5779             :     // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
    5780             :     // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
    5781             :     // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
    5782          40 :     m_dfPrecision =
    5783          40 :         CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
    5784             :     CPLString osGridCellEncoding(CSLFetchNameValueDef(
    5785          80 :         papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
    5786          40 :     m_bGridCellEncodingAsCO =
    5787          40 :         CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
    5788          80 :     CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
    5789             :     CPLString osFieldName(
    5790          80 :         CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
    5791             :     CPLString osQuantityDefinition(
    5792          80 :         CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
    5793             : 
    5794         121 :     pszSQL = sqlite3_mprintf(
    5795             :         "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
    5796             :         "(tile_matrix_set_name, datatype, scale, offset, precision, "
    5797             :         "grid_cell_encoding, uom, field_name, quantity_definition) "
    5798             :         "VALUES (%Q, '%s', %.17g, %.17g, %.17g, %Q, %Q, %Q, %Q)",
    5799             :         m_osRasterTable.c_str(),
    5800          40 :         (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
    5801             :         m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
    5802          41 :         osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
    5803             :         osQuantityDefinition.c_str());
    5804          40 :     m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
    5805          40 :     sqlite3_free(pszSQL);
    5806             : 
    5807             :     // Requirement 3 /gpkg-spatial-ref-sys-row
    5808             :     auto oResultTable = SQLQuery(
    5809          80 :         hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
    5810          40 :     bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
    5811          40 :     if (!bHasEPSG4979)
    5812             :     {
    5813          41 :         if (!m_bHasDefinition12_063 &&
    5814           1 :             !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
    5815             :         {
    5816           0 :             return false;
    5817             :         }
    5818             : 
    5819             :         // This is WKT 2...
    5820          40 :         const char *pszWKT =
    5821             :             "GEODCRS[\"WGS 84\","
    5822             :             "DATUM[\"World Geodetic System 1984\","
    5823             :             "  ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
    5824             :             "LENGTHUNIT[\"metre\",1.0]]],"
    5825             :             "CS[ellipsoidal,3],"
    5826             :             "  AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
    5827             :             "0.0174532925199433]],"
    5828             :             "  AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
    5829             :             "0.0174532925199433]],"
    5830             :             "  AXIS[\"ellipsoidal height\",up,ORDER[3],"
    5831             :             "LENGTHUNIT[\"metre\",1.0]],"
    5832             :             "ID[\"EPSG\",4979]]";
    5833             : 
    5834          40 :         pszSQL = sqlite3_mprintf(
    5835             :             "INSERT INTO gpkg_spatial_ref_sys "
    5836             :             "(srs_name,srs_id,organization,organization_coordsys_id,"
    5837             :             "definition,definition_12_063) VALUES "
    5838             :             "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
    5839             :             pszWKT);
    5840          40 :         osSQL += ";";
    5841          40 :         osSQL += pszSQL;
    5842          40 :         sqlite3_free(pszSQL);
    5843             :     }
    5844             : 
    5845          40 :     return SQLCommand(hDB, osSQL) == OGRERR_NONE;
    5846             : }
    5847             : 
    5848             : /************************************************************************/
    5849             : /*                  HasGriddedCoverageAncillaryTable()                  */
    5850             : /************************************************************************/
    5851             : 
    5852          44 : bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
    5853             : {
    5854             :     auto oResultTable = SQLQuery(
    5855             :         hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
    5856          44 :              "name = 'gpkg_2d_gridded_coverage_ancillary'");
    5857          44 :     bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
    5858          88 :     return bHasTable;
    5859             : }
    5860             : 
    5861             : /************************************************************************/
    5862             : /*                        GetUnderlyingDataset()                        */
    5863             : /************************************************************************/
    5864             : 
    5865           3 : static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
    5866             : {
    5867           3 :     if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
    5868             :     {
    5869           0 :         auto poTmpDS = poVRTDS->GetSingleSimpleSource();
    5870           0 :         if (poTmpDS)
    5871           0 :             return poTmpDS;
    5872             :     }
    5873             : 
    5874           3 :     return poSrcDS;
    5875             : }
    5876             : 
    5877             : /************************************************************************/
    5878             : /*                             CreateCopy()                             */
    5879             : /************************************************************************/
    5880             : 
    5881             : typedef struct
    5882             : {
    5883             :     const char *pszName;
    5884             :     GDALResampleAlg eResampleAlg;
    5885             : } WarpResamplingAlg;
    5886             : 
    5887             : static const WarpResamplingAlg asResamplingAlg[] = {
    5888             :     {"NEAREST", GRA_NearestNeighbour},
    5889             :     {"BILINEAR", GRA_Bilinear},
    5890             :     {"CUBIC", GRA_Cubic},
    5891             :     {"CUBICSPLINE", GRA_CubicSpline},
    5892             :     {"LANCZOS", GRA_Lanczos},
    5893             :     {"MODE", GRA_Mode},
    5894             :     {"AVERAGE", GRA_Average},
    5895             :     {"RMS", GRA_RMS},
    5896             : };
    5897             : 
    5898         162 : GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
    5899             :                                                GDALDataset *poSrcDS,
    5900             :                                                int bStrict,
    5901             :                                                CSLConstList papszOptions,
    5902             :                                                GDALProgressFunc pfnProgress,
    5903             :                                                void *pProgressData)
    5904             : {
    5905         162 :     const int nBands = poSrcDS->GetRasterCount();
    5906         162 :     if (nBands == 0)
    5907             :     {
    5908           2 :         GDALDataset *poDS = nullptr;
    5909             :         GDALDriver *poThisDriver =
    5910           2 :             GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
    5911           2 :         if (poThisDriver != nullptr)
    5912             :         {
    5913           2 :             poDS = poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS,
    5914             :                                                    bStrict, papszOptions,
    5915             :                                                    pfnProgress, pProgressData);
    5916             :         }
    5917           2 :         return poDS;
    5918             :     }
    5919             : 
    5920             :     const char *pszTilingScheme =
    5921         160 :         CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
    5922             : 
    5923         320 :     CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
    5924         160 :     if (CPLTestBool(
    5925         166 :             CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
    5926           6 :         CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
    5927             :     {
    5928             :         const std::string osBasename(CPLGetBasenameSafe(
    5929           6 :             GetUnderlyingDataset(poSrcDS)->GetDescription()));
    5930           3 :         apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename.c_str());
    5931             :     }
    5932             : 
    5933         160 :     if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
    5934             :     {
    5935           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    5936             :                  "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
    5937             :                  "4 (RGBA) band dataset supported");
    5938           1 :         return nullptr;
    5939             :     }
    5940             : 
    5941         159 :     const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
    5942         318 :     if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
    5943         159 :         !EQUAL(pszUnitType, ""))
    5944             :     {
    5945           1 :         apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
    5946             :     }
    5947             : 
    5948         159 :     if (EQUAL(pszTilingScheme, "CUSTOM"))
    5949             :     {
    5950         135 :         if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
    5951             :         {
    5952           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    5953             :                      "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
    5954           0 :             return nullptr;
    5955             :         }
    5956             : 
    5957         135 :         GDALGeoPackageDataset *poDS = nullptr;
    5958             :         GDALDriver *poThisDriver =
    5959         135 :             GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
    5960         135 :         if (poThisDriver != nullptr)
    5961             :         {
    5962         135 :             apszUpdatedOptions.SetNameValue("SKIP_HOLES", "YES");
    5963         135 :             poDS = cpl::down_cast<GDALGeoPackageDataset *>(
    5964             :                 poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
    5965             :                                                 apszUpdatedOptions, pfnProgress,
    5966         135 :                                                 pProgressData));
    5967             : 
    5968         250 :             if (poDS != nullptr &&
    5969         135 :                 poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_UInt8 &&
    5970             :                 nBands <= 3)
    5971             :             {
    5972          75 :                 poDS->m_nBandCountFromMetadata = nBands;
    5973          75 :                 poDS->m_bMetadataDirty = true;
    5974             :             }
    5975             :         }
    5976         135 :         if (poDS)
    5977         115 :             poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    5978         135 :         return poDS;
    5979             :     }
    5980             : 
    5981          48 :     const auto poTS = GetTilingScheme(pszTilingScheme);
    5982          24 :     if (!poTS)
    5983             :     {
    5984           2 :         return nullptr;
    5985             :     }
    5986          22 :     const int nEPSGCode = poTS->nEPSGCode;
    5987             : 
    5988          44 :     OGRSpatialReference oSRS;
    5989          22 :     if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
    5990             :     {
    5991           0 :         return nullptr;
    5992             :     }
    5993          22 :     char *pszWKT = nullptr;
    5994          22 :     oSRS.exportToWkt(&pszWKT);
    5995          22 :     char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
    5996             : 
    5997          22 :     void *hTransformArg = nullptr;
    5998             : 
    5999             :     // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
    6000             :     // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
    6001             :     // EPSG:3857.
    6002          22 :     GDALGeoTransform srcGT;
    6003          22 :     std::unique_ptr<GDALDataset> poTmpDS;
    6004          22 :     bool bEPSG3857Adjust = false;
    6005           8 :     if (nEPSGCode == 3857 && poSrcDS->GetGeoTransform(srcGT) == CE_None &&
    6006          30 :         srcGT[2] == 0 && srcGT[4] == 0 && srcGT[5] < 0)
    6007             :     {
    6008           8 :         const auto poSrcSRS = poSrcDS->GetSpatialRef();
    6009           8 :         if (poSrcSRS && poSrcSRS->IsGeographic())
    6010             :         {
    6011           2 :             double maxLat = srcGT[3];
    6012           2 :             double minLat = srcGT[3] + poSrcDS->GetRasterYSize() * srcGT[5];
    6013             :             // Corresponds to the latitude of below MAX_GM
    6014           2 :             constexpr double MAX_LAT = 85.0511287798066;
    6015           2 :             bool bModified = false;
    6016           2 :             if (maxLat > MAX_LAT)
    6017             :             {
    6018           2 :                 maxLat = MAX_LAT;
    6019           2 :                 bModified = true;
    6020             :             }
    6021           2 :             if (minLat < -MAX_LAT)
    6022             :             {
    6023           2 :                 minLat = -MAX_LAT;
    6024           2 :                 bModified = true;
    6025             :             }
    6026           2 :             if (bModified)
    6027             :             {
    6028           4 :                 CPLStringList aosOptions;
    6029           2 :                 aosOptions.AddString("-of");
    6030           2 :                 aosOptions.AddString("VRT");
    6031           2 :                 aosOptions.AddString("-projwin");
    6032           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", srcGT[0]));
    6033           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
    6034             :                 aosOptions.AddString(CPLSPrintf(
    6035           2 :                     "%.17g", srcGT[0] + poSrcDS->GetRasterXSize() * srcGT[1]));
    6036           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", minLat));
    6037             :                 auto psOptions =
    6038           2 :                     GDALTranslateOptionsNew(aosOptions.List(), nullptr);
    6039           2 :                 poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
    6040             :                     "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
    6041           2 :                 GDALTranslateOptionsFree(psOptions);
    6042           2 :                 if (poTmpDS)
    6043             :                 {
    6044           2 :                     bEPSG3857Adjust = true;
    6045           2 :                     hTransformArg = GDALCreateGenImgProjTransformer2(
    6046           2 :                         GDALDataset::FromHandle(poTmpDS.get()), nullptr,
    6047             :                         papszTO);
    6048             :                 }
    6049             :             }
    6050             :         }
    6051             :     }
    6052          22 :     if (hTransformArg == nullptr)
    6053             :     {
    6054             :         hTransformArg =
    6055          20 :             GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
    6056             :     }
    6057             : 
    6058          22 :     if (hTransformArg == nullptr)
    6059             :     {
    6060           1 :         CPLFree(pszWKT);
    6061           1 :         CSLDestroy(papszTO);
    6062           1 :         return nullptr;
    6063             :     }
    6064             : 
    6065          21 :     GDALTransformerInfo *psInfo =
    6066             :         static_cast<GDALTransformerInfo *>(hTransformArg);
    6067          21 :     GDALGeoTransform gt;
    6068             :     double adfExtent[4];
    6069             :     int nXSize, nYSize;
    6070             : 
    6071          21 :     if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
    6072             :                                  gt.data(), &nXSize, &nYSize, adfExtent,
    6073          21 :                                  0) != CE_None)
    6074             :     {
    6075           0 :         CPLFree(pszWKT);
    6076           0 :         CSLDestroy(papszTO);
    6077           0 :         GDALDestroyGenImgProjTransformer(hTransformArg);
    6078           0 :         return nullptr;
    6079             :     }
    6080             : 
    6081          21 :     GDALDestroyGenImgProjTransformer(hTransformArg);
    6082          21 :     hTransformArg = nullptr;
    6083          21 :     poTmpDS.reset();
    6084             : 
    6085          21 :     if (bEPSG3857Adjust)
    6086             :     {
    6087           2 :         constexpr double SPHERICAL_RADIUS = 6378137.0;
    6088           2 :         constexpr double MAX_GM =
    6089             :             SPHERICAL_RADIUS * M_PI;  // 20037508.342789244
    6090           2 :         double maxNorthing = gt[3];
    6091           2 :         double minNorthing = gt[3] + gt[5] * nYSize;
    6092           2 :         bool bChanged = false;
    6093           2 :         if (maxNorthing > MAX_GM)
    6094             :         {
    6095           2 :             bChanged = true;
    6096           2 :             maxNorthing = MAX_GM;
    6097             :         }
    6098           2 :         if (minNorthing < -MAX_GM)
    6099             :         {
    6100           2 :             bChanged = true;
    6101           2 :             minNorthing = -MAX_GM;
    6102             :         }
    6103           2 :         if (bChanged)
    6104             :         {
    6105           2 :             gt[3] = maxNorthing;
    6106           2 :             nYSize = int((maxNorthing - minNorthing) / (-gt[5]) + 0.5);
    6107           2 :             adfExtent[1] = maxNorthing + nYSize * gt[5];
    6108           2 :             adfExtent[3] = maxNorthing;
    6109             :         }
    6110             :     }
    6111             : 
    6112          21 :     double dfComputedRes = gt[1];
    6113          21 :     double dfPrevRes = 0.0;
    6114          21 :     double dfRes = 0.0;
    6115          21 :     int nZoomLevel = 0;  // Used after for.
    6116          21 :     const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
    6117          21 :     if (pszZoomLevel)
    6118             :     {
    6119           2 :         nZoomLevel = atoi(pszZoomLevel);
    6120             : 
    6121           2 :         int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
    6122           2 :         while ((1 << nMaxZoomLevelForThisTM) >
    6123           4 :                    INT_MAX / poTS->nTileXCountZoomLevel0 ||
    6124           2 :                (1 << nMaxZoomLevelForThisTM) >
    6125           2 :                    INT_MAX / poTS->nTileYCountZoomLevel0)
    6126             :         {
    6127           0 :             --nMaxZoomLevelForThisTM;
    6128             :         }
    6129             : 
    6130           2 :         if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
    6131             :         {
    6132           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6133             :                      "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
    6134             :                      pszZoomLevel, nMaxZoomLevelForThisTM);
    6135           1 :             CPLFree(pszWKT);
    6136           1 :             CSLDestroy(papszTO);
    6137           1 :             return nullptr;
    6138             :         }
    6139             :     }
    6140             :     else
    6141             :     {
    6142         171 :         for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
    6143             :         {
    6144         171 :             dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
    6145         171 :             if (dfComputedRes > dfRes ||
    6146         152 :                 fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
    6147             :                 break;
    6148         152 :             dfPrevRes = dfRes;
    6149             :         }
    6150          38 :         if (nZoomLevel == MAX_ZOOM_LEVEL ||
    6151          38 :             (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
    6152          19 :             (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
    6153             :         {
    6154           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6155             :                      "Could not find an appropriate zoom level");
    6156           0 :             CPLFree(pszWKT);
    6157           0 :             CSLDestroy(papszTO);
    6158           0 :             return nullptr;
    6159             :         }
    6160             : 
    6161          19 :         if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
    6162             :         {
    6163          17 :             const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
    6164             :                 papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
    6165          17 :             if (EQUAL(pszZoomLevelStrategy, "LOWER"))
    6166             :             {
    6167           1 :                 nZoomLevel--;
    6168             :             }
    6169          16 :             else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
    6170             :             {
    6171             :                 /* do nothing */
    6172             :             }
    6173             :             else
    6174             :             {
    6175          15 :                 if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
    6176          13 :                     nZoomLevel--;
    6177             :             }
    6178             :         }
    6179             :     }
    6180             : 
    6181          20 :     dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
    6182             : 
    6183          20 :     double dfMinX = adfExtent[0];
    6184          20 :     double dfMinY = adfExtent[1];
    6185          20 :     double dfMaxX = adfExtent[2];
    6186          20 :     double dfMaxY = adfExtent[3];
    6187             : 
    6188          20 :     nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
    6189          20 :     nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
    6190          20 :     gt[1] = dfRes;
    6191          20 :     gt[5] = -dfRes;
    6192             : 
    6193          20 :     const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
    6194          20 :     int nTargetBands = nBands;
    6195             :     /* For grey level or RGB, if there's reprojection involved, add an alpha */
    6196             :     /* channel */
    6197          37 :     if (eDT == GDT_UInt8 &&
    6198          13 :         ((nBands == 1 &&
    6199          17 :           poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
    6200             :          nBands == 3))
    6201             :     {
    6202          30 :         OGRSpatialReference oSrcSRS;
    6203          15 :         oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
    6204          15 :         oSrcSRS.AutoIdentifyEPSG();
    6205          30 :         if (oSrcSRS.GetAuthorityCode(nullptr) == nullptr ||
    6206          15 :             atoi(oSrcSRS.GetAuthorityCode(nullptr)) != nEPSGCode)
    6207             :         {
    6208          13 :             nTargetBands++;
    6209             :         }
    6210             :     }
    6211             : 
    6212          20 :     GDALResampleAlg eResampleAlg = GRA_Bilinear;
    6213          20 :     const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
    6214          20 :     if (pszResampling)
    6215             :     {
    6216           6 :         for (size_t iAlg = 0;
    6217           6 :              iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
    6218             :              iAlg++)
    6219             :         {
    6220           6 :             if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
    6221             :             {
    6222           3 :                 eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
    6223           3 :                 break;
    6224             :             }
    6225             :         }
    6226             :     }
    6227             : 
    6228          16 :     if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
    6229          36 :         eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
    6230             :     {
    6231           0 :         CPLError(
    6232             :             CE_Warning, CPLE_AppDefined,
    6233             :             "Input dataset has a color table, which will likely lead to "
    6234             :             "bad results when using a resampling method other than "
    6235             :             "nearest neighbour or mode. Converting the dataset to 24/32 bit "
    6236             :             "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
    6237             :     }
    6238             : 
    6239          40 :     auto poDS = std::make_unique<GDALGeoPackageDataset>();
    6240          40 :     if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
    6241          20 :                        apszUpdatedOptions)))
    6242             :     {
    6243           1 :         CPLFree(pszWKT);
    6244           1 :         CSLDestroy(papszTO);
    6245           1 :         return nullptr;
    6246             :     }
    6247             : 
    6248             :     // Assign nodata values before the SetGeoTransform call.
    6249             :     // SetGeoTransform will trigger creation of the overview datasets for each
    6250             :     // zoom level and at that point the nodata value needs to be known.
    6251          19 :     int bHasNoData = FALSE;
    6252             :     double dfNoDataValue =
    6253          19 :         poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    6254          19 :     if (eDT != GDT_UInt8 && bHasNoData)
    6255             :     {
    6256           3 :         poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
    6257             :     }
    6258             : 
    6259          19 :     poDS->SetGeoTransform(gt);
    6260          19 :     poDS->SetProjection(pszWKT);
    6261          19 :     CPLFree(pszWKT);
    6262          19 :     pszWKT = nullptr;
    6263          24 :     if (nTargetBands == 1 && nBands == 1 &&
    6264           5 :         poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
    6265             :     {
    6266           2 :         poDS->GetRasterBand(1)->SetColorTable(
    6267           1 :             poSrcDS->GetRasterBand(1)->GetColorTable());
    6268             :     }
    6269             : 
    6270             :     hTransformArg =
    6271          19 :         GDALCreateGenImgProjTransformer2(poSrcDS, poDS.get(), papszTO);
    6272          19 :     CSLDestroy(papszTO);
    6273          19 :     if (hTransformArg == nullptr)
    6274             :     {
    6275           0 :         return nullptr;
    6276             :     }
    6277             : 
    6278          19 :     poDS->SetMetadata(poSrcDS->GetMetadata());
    6279             : 
    6280             :     /* -------------------------------------------------------------------- */
    6281             :     /*      Warp the transformer with a linear approximator                 */
    6282             :     /* -------------------------------------------------------------------- */
    6283          19 :     hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
    6284             :                                                 hTransformArg, 0.125);
    6285          19 :     GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
    6286             : 
    6287             :     /* -------------------------------------------------------------------- */
    6288             :     /*      Setup warp options.                                             */
    6289             :     /* -------------------------------------------------------------------- */
    6290          19 :     GDALWarpOptions *psWO = GDALCreateWarpOptions();
    6291             : 
    6292          19 :     psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
    6293          19 :     psWO->papszWarpOptions =
    6294          19 :         CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
    6295          19 :     if (bHasNoData)
    6296             :     {
    6297           3 :         if (dfNoDataValue == 0.0)
    6298             :         {
    6299             :             // Do not initialize in the case where nodata != 0, since we
    6300             :             // want the GeoPackage driver to return empty tiles at the nodata
    6301             :             // value instead of 0 as GDAL core would
    6302           0 :             psWO->papszWarpOptions =
    6303           0 :                 CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
    6304             :         }
    6305             : 
    6306           3 :         psWO->padfSrcNoDataReal =
    6307           3 :             static_cast<double *>(CPLMalloc(sizeof(double)));
    6308           3 :         psWO->padfSrcNoDataReal[0] = dfNoDataValue;
    6309             : 
    6310           3 :         psWO->padfDstNoDataReal =
    6311           3 :             static_cast<double *>(CPLMalloc(sizeof(double)));
    6312           3 :         psWO->padfDstNoDataReal[0] = dfNoDataValue;
    6313             :     }
    6314          19 :     psWO->eWorkingDataType = eDT;
    6315          19 :     psWO->eResampleAlg = eResampleAlg;
    6316             : 
    6317          19 :     psWO->hSrcDS = poSrcDS;
    6318          19 :     psWO->hDstDS = poDS.get();
    6319             : 
    6320          19 :     psWO->pfnTransformer = GDALApproxTransform;
    6321          19 :     psWO->pTransformerArg = hTransformArg;
    6322             : 
    6323          19 :     psWO->pfnProgress = pfnProgress;
    6324          19 :     psWO->pProgressArg = pProgressData;
    6325             : 
    6326             :     /* -------------------------------------------------------------------- */
    6327             :     /*      Setup band mapping.                                             */
    6328             :     /* -------------------------------------------------------------------- */
    6329             : 
    6330          19 :     if (nBands == 2 || nBands == 4)
    6331           1 :         psWO->nBandCount = nBands - 1;
    6332             :     else
    6333          18 :         psWO->nBandCount = nBands;
    6334             : 
    6335          19 :     psWO->panSrcBands =
    6336          19 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    6337          19 :     psWO->panDstBands =
    6338          19 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    6339             : 
    6340          46 :     for (int i = 0; i < psWO->nBandCount; i++)
    6341             :     {
    6342          27 :         psWO->panSrcBands[i] = i + 1;
    6343          27 :         psWO->panDstBands[i] = i + 1;
    6344             :     }
    6345             : 
    6346          19 :     if (nBands == 2 || nBands == 4)
    6347             :     {
    6348           1 :         psWO->nSrcAlphaBand = nBands;
    6349             :     }
    6350          19 :     if (nTargetBands == 2 || nTargetBands == 4)
    6351             :     {
    6352          13 :         psWO->nDstAlphaBand = nTargetBands;
    6353             :     }
    6354             : 
    6355             :     /* -------------------------------------------------------------------- */
    6356             :     /*      Initialize and execute the warp.                                */
    6357             :     /* -------------------------------------------------------------------- */
    6358          38 :     GDALWarpOperation oWO;
    6359             : 
    6360          19 :     CPLErr eErr = oWO.Initialize(psWO);
    6361          19 :     if (eErr == CE_None)
    6362             :     {
    6363             :         /*if( bMulti )
    6364             :             eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
    6365             :         else*/
    6366          19 :         eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
    6367             :     }
    6368          19 :     if (eErr != CE_None)
    6369             :     {
    6370           0 :         poDS.reset();
    6371             :     }
    6372             : 
    6373          19 :     GDALDestroyTransformer(hTransformArg);
    6374          19 :     GDALDestroyWarpOptions(psWO);
    6375             : 
    6376          19 :     if (poDS)
    6377          19 :         poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    6378             : 
    6379          19 :     return poDS.release();
    6380             : }
    6381             : 
    6382             : /************************************************************************/
    6383             : /*                      ParseCompressionOptions()                       */
    6384             : /************************************************************************/
    6385             : 
    6386         464 : void GDALGeoPackageDataset::ParseCompressionOptions(CSLConstList papszOptions)
    6387             : {
    6388         464 :     const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
    6389         464 :     if (pszZLevel)
    6390           0 :         m_nZLevel = atoi(pszZLevel);
    6391             : 
    6392         464 :     const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
    6393         464 :     if (pszQuality)
    6394           0 :         m_nQuality = atoi(pszQuality);
    6395             : 
    6396         464 :     const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
    6397         464 :     if (pszDither)
    6398           0 :         m_bDither = CPLTestBool(pszDither);
    6399         464 : }
    6400             : 
    6401             : /************************************************************************/
    6402             : /*                       RegisterWebPExtension()                        */
    6403             : /************************************************************************/
    6404             : 
    6405          11 : bool GDALGeoPackageDataset::RegisterWebPExtension()
    6406             : {
    6407          11 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    6408           0 :         return false;
    6409             : 
    6410          11 :     char *pszSQL = sqlite3_mprintf(
    6411             :         "INSERT INTO gpkg_extensions "
    6412             :         "(table_name, column_name, extension_name, definition, scope) "
    6413             :         "VALUES "
    6414             :         "('%q', 'tile_data', 'gpkg_webp', "
    6415             :         "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
    6416             :         "'read-write')",
    6417             :         m_osRasterTable.c_str());
    6418          11 :     const OGRErr eErr = SQLCommand(hDB, pszSQL);
    6419          11 :     sqlite3_free(pszSQL);
    6420             : 
    6421          11 :     return OGRERR_NONE == eErr;
    6422             : }
    6423             : 
    6424             : /************************************************************************/
    6425             : /*                     RegisterZoomOtherExtension()                     */
    6426             : /************************************************************************/
    6427             : 
    6428           1 : bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
    6429             : {
    6430           1 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    6431           0 :         return false;
    6432             : 
    6433           1 :     char *pszSQL = sqlite3_mprintf(
    6434             :         "INSERT INTO gpkg_extensions "
    6435             :         "(table_name, column_name, extension_name, definition, scope) "
    6436             :         "VALUES "
    6437             :         "('%q', 'tile_data', 'gpkg_zoom_other', "
    6438             :         "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
    6439             :         "'read-write')",
    6440             :         m_osRasterTable.c_str());
    6441           1 :     const OGRErr eErr = SQLCommand(hDB, pszSQL);
    6442           1 :     sqlite3_free(pszSQL);
    6443           1 :     return OGRERR_NONE == eErr;
    6444             : }
    6445             : 
    6446             : /************************************************************************/
    6447             : /*                              GetLayer()                              */
    6448             : /************************************************************************/
    6449             : 
    6450       16101 : const OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer) const
    6451             : 
    6452             : {
    6453       16101 :     if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
    6454           7 :         return nullptr;
    6455             :     else
    6456       16094 :         return m_apoLayers[iLayer].get();
    6457             : }
    6458             : 
    6459             : /************************************************************************/
    6460             : /*                            LaunderName()                             */
    6461             : /************************************************************************/
    6462             : 
    6463             : /** Launder identifiers (table, column names) according to guidance at
    6464             :  * https://www.geopackage.org/guidance/getting-started.html:
    6465             :  * "For maximum interoperability, start your database identifiers (table names,
    6466             :  * column names, etc.) with a lowercase character and only use lowercase
    6467             :  * characters, numbers 0-9, and underscores (_)."
    6468             :  */
    6469             : 
    6470             : /* static */
    6471           5 : std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
    6472             : {
    6473           5 :     char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
    6474          10 :     const std::string osStrASCII(pszASCII);
    6475           5 :     CPLFree(pszASCII);
    6476             : 
    6477          10 :     std::string osRet;
    6478           5 :     osRet.reserve(osStrASCII.size());
    6479             : 
    6480          29 :     for (size_t i = 0; i < osStrASCII.size(); ++i)
    6481             :     {
    6482          24 :         if (osRet.empty())
    6483             :         {
    6484           5 :             if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
    6485             :             {
    6486           2 :                 osRet += (osStrASCII[i] - 'A' + 'a');
    6487             :             }
    6488           3 :             else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
    6489             :             {
    6490           2 :                 osRet += osStrASCII[i];
    6491             :             }
    6492             :             else
    6493             :             {
    6494           1 :                 continue;
    6495             :             }
    6496             :         }
    6497          19 :         else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
    6498             :         {
    6499          11 :             osRet += (osStrASCII[i] - 'A' + 'a');
    6500             :         }
    6501           9 :         else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
    6502          14 :                  (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
    6503           5 :                  osStrASCII[i] == '_')
    6504             :         {
    6505           7 :             osRet += osStrASCII[i];
    6506             :         }
    6507             :         else
    6508             :         {
    6509           1 :             osRet += '_';
    6510             :         }
    6511             :     }
    6512             : 
    6513           5 :     if (osRet.empty() && !osStrASCII.empty())
    6514           2 :         return LaunderName(std::string("x").append(osStrASCII));
    6515             : 
    6516           4 :     if (osRet != osStr)
    6517             :     {
    6518           3 :         CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
    6519             :                  osRet.c_str());
    6520             :     }
    6521             : 
    6522           4 :     return osRet;
    6523             : }
    6524             : 
    6525             : /************************************************************************/
    6526             : /*                            ICreateLayer()                            */
    6527             : /************************************************************************/
    6528             : 
    6529             : OGRLayer *
    6530         888 : GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
    6531             :                                     const OGRGeomFieldDefn *poSrcGeomFieldDefn,
    6532             :                                     CSLConstList papszOptions)
    6533             : {
    6534             :     /* -------------------------------------------------------------------- */
    6535             :     /*      Verify we are in update mode.                                   */
    6536             :     /* -------------------------------------------------------------------- */
    6537         888 :     if (!GetUpdate())
    6538             :     {
    6539           0 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
    6540             :                  "Data source %s opened read-only.\n"
    6541             :                  "New layer %s cannot be created.\n",
    6542             :                  m_pszFilename, pszLayerName);
    6543             : 
    6544           0 :         return nullptr;
    6545             :     }
    6546             : 
    6547             :     const bool bLaunder =
    6548         888 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
    6549             :     const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
    6550        2664 :                                            : std::string(pszLayerName));
    6551             : 
    6552             :     const auto eGType =
    6553         888 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
    6554             :     const auto poSpatialRef =
    6555         888 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
    6556             : 
    6557         888 :     if (!m_bHasGPKGGeometryColumns)
    6558             :     {
    6559           1 :         if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
    6560             :         {
    6561           0 :             return nullptr;
    6562             :         }
    6563           1 :         m_bHasGPKGGeometryColumns = true;
    6564             :     }
    6565             : 
    6566             :     // Check identifier unicity
    6567         888 :     const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
    6568         888 :     if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
    6569           0 :         pszIdentifier = nullptr;
    6570         888 :     if (pszIdentifier != nullptr)
    6571             :     {
    6572          13 :         for (auto &poLayer : m_apoLayers)
    6573             :         {
    6574             :             const char *pszOtherIdentifier =
    6575           9 :                 poLayer->GetMetadataItem("IDENTIFIER");
    6576           9 :             if (pszOtherIdentifier == nullptr)
    6577           6 :                 pszOtherIdentifier = poLayer->GetName();
    6578          18 :             if (pszOtherIdentifier != nullptr &&
    6579          12 :                 EQUAL(pszOtherIdentifier, pszIdentifier) &&
    6580           3 :                 !EQUAL(poLayer->GetName(), osTableName.c_str()))
    6581             :             {
    6582           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    6583             :                          "Identifier %s is already used by table %s",
    6584             :                          pszIdentifier, poLayer->GetName());
    6585           2 :                 return nullptr;
    6586             :             }
    6587             :         }
    6588             : 
    6589             :         // In case there would be table in gpkg_contents not listed as a
    6590             :         // vector layer
    6591           4 :         char *pszSQL = sqlite3_mprintf(
    6592             :             "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
    6593             :             "LIMIT 2",
    6594             :             pszIdentifier);
    6595           4 :         auto oResult = SQLQuery(hDB, pszSQL);
    6596           4 :         sqlite3_free(pszSQL);
    6597           8 :         if (oResult && oResult->RowCount() > 0 &&
    6598           9 :             oResult->GetValue(0, 0) != nullptr &&
    6599           1 :             !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
    6600             :         {
    6601           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6602             :                      "Identifier %s is already used by table %s", pszIdentifier,
    6603             :                      oResult->GetValue(0, 0));
    6604           1 :             return nullptr;
    6605             :         }
    6606             :     }
    6607             : 
    6608             :     /* Read GEOMETRY_NAME option */
    6609             :     const char *pszGeomColumnName =
    6610         885 :         CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
    6611         885 :     if (pszGeomColumnName == nullptr) /* deprecated name */
    6612         799 :         pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
    6613         885 :     if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
    6614             :     {
    6615         720 :         pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
    6616         720 :         if (pszGeomColumnName && pszGeomColumnName[0] == 0)
    6617         716 :             pszGeomColumnName = nullptr;
    6618             :     }
    6619         885 :     if (pszGeomColumnName == nullptr)
    6620         795 :         pszGeomColumnName = "geom";
    6621             :     const bool bGeomNullable =
    6622         885 :         CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
    6623             : 
    6624             :     /* Read FID option */
    6625         885 :     const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
    6626         885 :     if (pszFIDColumnName == nullptr)
    6627         802 :         pszFIDColumnName = "fid";
    6628             : 
    6629         885 :     if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
    6630             :     {
    6631         885 :         if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
    6632             :         {
    6633           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    6634             :                      "The primary key (%s) name may not contain special "
    6635             :                      "characters or spaces",
    6636             :                      pszFIDColumnName);
    6637           2 :             return nullptr;
    6638             :         }
    6639             : 
    6640             :         /* Avoiding gpkg prefixes is not an official requirement, but seems wise
    6641             :          */
    6642         883 :         if (STARTS_WITH(osTableName.c_str(), "gpkg"))
    6643             :         {
    6644           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6645             :                      "The layer name may not begin with 'gpkg' as it is a "
    6646             :                      "reserved geopackage prefix");
    6647           0 :             return nullptr;
    6648             :         }
    6649             : 
    6650             :         /* Preemptively try and avoid sqlite3 syntax errors due to  */
    6651             :         /* illegal characters. */
    6652         883 :         if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
    6653             :             0)
    6654             :         {
    6655           0 :             CPLError(
    6656             :                 CE_Failure, CPLE_AppDefined,
    6657             :                 "The layer name may not contain special characters or spaces");
    6658           0 :             return nullptr;
    6659             :         }
    6660             :     }
    6661             : 
    6662             :     /* Check for any existing layers that already use this name */
    6663        1092 :     for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
    6664             :          iLayer++)
    6665             :     {
    6666         210 :         if (EQUAL(osTableName.c_str(), m_apoLayers[iLayer]->GetName()))
    6667             :         {
    6668             :             const char *pszOverwrite =
    6669           2 :                 CSLFetchNameValue(papszOptions, "OVERWRITE");
    6670           2 :             if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
    6671             :             {
    6672           1 :                 DeleteLayer(iLayer);
    6673             :             }
    6674             :             else
    6675             :             {
    6676           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    6677             :                          "Layer %s already exists, CreateLayer failed.\n"
    6678             :                          "Use the layer creation option OVERWRITE=YES to "
    6679             :                          "replace it.",
    6680             :                          osTableName.c_str());
    6681           1 :                 return nullptr;
    6682             :             }
    6683             :         }
    6684             :     }
    6685             : 
    6686         882 :     if (m_apoLayers.size() == 1)
    6687             :     {
    6688             :         // Async RTree building doesn't play well with multiple layer:
    6689             :         // SQLite3 locks being hold for a long time, random failed commits,
    6690             :         // etc.
    6691          83 :         m_apoLayers[0]->FinishOrDisableThreadedRTree();
    6692             :     }
    6693             : 
    6694             :     /* Create a blank layer. */
    6695             :     auto poLayer =
    6696        1764 :         std::make_unique<OGRGeoPackageTableLayer>(this, osTableName.c_str());
    6697             : 
    6698         882 :     OGRSpatialReference *poSRS = nullptr;
    6699         882 :     if (poSpatialRef)
    6700             :     {
    6701         267 :         poSRS = poSpatialRef->Clone();
    6702         267 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6703             :     }
    6704        1765 :     poLayer->SetCreationParameters(
    6705             :         eGType,
    6706         883 :         bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
    6707             :         bGeomNullable, poSRS, CSLFetchNameValue(papszOptions, "SRID"),
    6708        1764 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
    6709             :                            : OGRGeomCoordinatePrecision(),
    6710         882 :         CPLTestBool(
    6711             :             CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
    6712         882 :         CPLTestBool(CSLFetchNameValueDef(
    6713             :             papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
    6714         883 :         bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
    6715             :         pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
    6716         882 :     if (poSRS)
    6717             :     {
    6718         267 :         poSRS->Release();
    6719             :     }
    6720             : 
    6721         882 :     poLayer->SetLaunder(bLaunder);
    6722             : 
    6723             :     /* Should we create a spatial index ? */
    6724         882 :     const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
    6725         882 :     int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
    6726         882 :     if (eGType != wkbNone && bCreateSpatialIndex)
    6727             :     {
    6728         781 :         poLayer->SetDeferredSpatialIndexCreation(true);
    6729             :     }
    6730             : 
    6731         882 :     poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
    6732         882 :     poLayer->SetTruncateFieldsFlag(
    6733         882 :         CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
    6734         882 :     if (eGType == wkbNone)
    6735             :     {
    6736          79 :         const char *pszASpatialVariant = CSLFetchNameValueDef(
    6737             :             papszOptions, "ASPATIAL_VARIANT",
    6738          79 :             m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
    6739             :                 ? "NOT_REGISTERED"
    6740             :                 : "GPKG_ATTRIBUTES");
    6741          79 :         GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
    6742          79 :         if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
    6743          67 :             eASpatialVariant = GPKG_ATTRIBUTES;
    6744          12 :         else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
    6745             :         {
    6746           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    6747             :                      "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
    6748           0 :             return nullptr;
    6749             :         }
    6750          12 :         else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
    6751          12 :             eASpatialVariant = NOT_REGISTERED;
    6752             :         else
    6753             :         {
    6754           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    6755             :                      "Unsupported value for ASPATIAL_VARIANT: %s",
    6756             :                      pszASpatialVariant);
    6757           0 :             return nullptr;
    6758             :         }
    6759          79 :         poLayer->SetASpatialVariant(eASpatialVariant);
    6760             :     }
    6761             : 
    6762             :     const char *pszDateTimePrecision =
    6763         882 :         CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
    6764         882 :     if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
    6765             :     {
    6766           2 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
    6767             :     }
    6768         880 :     else if (EQUAL(pszDateTimePrecision, "SECOND"))
    6769             :     {
    6770           1 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6771           0 :             CPLError(
    6772             :                 CE_Warning, CPLE_AppDefined,
    6773             :                 "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
    6774           1 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
    6775             :     }
    6776         879 :     else if (EQUAL(pszDateTimePrecision, "MINUTE"))
    6777             :     {
    6778           1 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6779           0 :             CPLError(
    6780             :                 CE_Warning, CPLE_AppDefined,
    6781             :                 "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
    6782           1 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
    6783             :     }
    6784         878 :     else if (EQUAL(pszDateTimePrecision, "AUTO"))
    6785             :     {
    6786         877 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6787          13 :             poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
    6788             :     }
    6789             :     else
    6790             :     {
    6791           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    6792             :                  "Unsupported value for DATETIME_PRECISION: %s",
    6793             :                  pszDateTimePrecision);
    6794           1 :         return nullptr;
    6795             :     }
    6796             : 
    6797             :     // If there was an ogr_empty_table table, we can remove it
    6798             :     // But do it at dataset closing, otherwise locking performance issues
    6799             :     // can arise (probably when transactions are used).
    6800         881 :     m_bRemoveOGREmptyTable = true;
    6801             : 
    6802         881 :     m_apoLayers.emplace_back(std::move(poLayer));
    6803         881 :     return m_apoLayers.back().get();
    6804             : }
    6805             : 
    6806             : /************************************************************************/
    6807             : /*                           FindLayerIndex()                           */
    6808             : /************************************************************************/
    6809             : 
    6810          27 : int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
    6811             : 
    6812             : {
    6813          42 :     for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
    6814             :          iLayer++)
    6815             :     {
    6816          28 :         if (EQUAL(pszLayerName, m_apoLayers[iLayer]->GetName()))
    6817          13 :             return iLayer;
    6818             :     }
    6819          14 :     return -1;
    6820             : }
    6821             : 
    6822             : /************************************************************************/
    6823             : /*                         DeleteLayerCommon()                          */
    6824             : /************************************************************************/
    6825             : 
    6826          42 : OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
    6827             : {
    6828             :     // Temporary remove foreign key checks
    6829             :     const GPKGTemporaryForeignKeyCheckDisabler
    6830          42 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    6831             : 
    6832          42 :     char *pszSQL = sqlite3_mprintf(
    6833             :         "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
    6834             :         pszLayerName);
    6835          42 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
    6836          42 :     sqlite3_free(pszSQL);
    6837             : 
    6838          42 :     if (eErr == OGRERR_NONE && HasExtensionsTable())
    6839             :     {
    6840          40 :         pszSQL = sqlite3_mprintf(
    6841             :             "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
    6842             :             pszLayerName);
    6843          40 :         eErr = SQLCommand(hDB, pszSQL);
    6844          40 :         sqlite3_free(pszSQL);
    6845             :     }
    6846             : 
    6847          42 :     if (eErr == OGRERR_NONE && HasMetadataTables())
    6848             :     {
    6849             :         // Delete from gpkg_metadata metadata records that are only referenced
    6850             :         // by the table we are about to drop
    6851          12 :         pszSQL = sqlite3_mprintf(
    6852             :             "DELETE FROM gpkg_metadata WHERE id IN ("
    6853             :             "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 id NOT IN ("
    6857             :             "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
    6858             :             "md_file_id IN (SELECT DISTINCT md_file_id FROM "
    6859             :             "gpkg_metadata_reference WHERE "
    6860             :             "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
    6861             :             "AND lower(table_name) <> lower('%q'))",
    6862             :             pszLayerName, pszLayerName, pszLayerName);
    6863          12 :         eErr = SQLCommand(hDB, pszSQL);
    6864          12 :         sqlite3_free(pszSQL);
    6865             : 
    6866          12 :         if (eErr == OGRERR_NONE)
    6867             :         {
    6868             :             pszSQL =
    6869          12 :                 sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
    6870             :                                 "lower(table_name) = lower('%q')",
    6871             :                                 pszLayerName);
    6872          12 :             eErr = SQLCommand(hDB, pszSQL);
    6873          12 :             sqlite3_free(pszSQL);
    6874             :         }
    6875             :     }
    6876             : 
    6877          42 :     if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
    6878             :     {
    6879             :         // Remove reference to potential corresponding mapping table in
    6880             :         // gpkg_extensions
    6881           4 :         pszSQL = sqlite3_mprintf(
    6882             :             "DELETE FROM gpkg_extensions WHERE "
    6883             :             "extension_name IN ('related_tables', "
    6884             :             "'gpkg_related_tables') AND lower(table_name) = "
    6885             :             "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
    6886             :             "lower(base_table_name) = lower('%q') OR "
    6887             :             "lower(related_table_name) = lower('%q') OR "
    6888             :             "lower(mapping_table_name) = lower('%q'))",
    6889             :             pszLayerName, pszLayerName, pszLayerName);
    6890           4 :         eErr = SQLCommand(hDB, pszSQL);
    6891           4 :         sqlite3_free(pszSQL);
    6892             : 
    6893           4 :         if (eErr == OGRERR_NONE)
    6894             :         {
    6895             :             // Remove reference to potential corresponding mapping table in
    6896             :             // gpkgext_relations
    6897             :             pszSQL =
    6898           4 :                 sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
    6899             :                                 "lower(base_table_name) = lower('%q') OR "
    6900             :                                 "lower(related_table_name) = lower('%q') OR "
    6901             :                                 "lower(mapping_table_name) = lower('%q')",
    6902             :                                 pszLayerName, pszLayerName, pszLayerName);
    6903           4 :             eErr = SQLCommand(hDB, pszSQL);
    6904           4 :             sqlite3_free(pszSQL);
    6905             :         }
    6906             : 
    6907           4 :         if (eErr == OGRERR_NONE && HasExtensionsTable())
    6908             :         {
    6909             :             // If there is no longer any mapping table, then completely
    6910             :             // remove any reference to the extension in gpkg_extensions
    6911             :             // as mandated per the related table specification.
    6912             :             OGRErr err;
    6913           4 :             if (SQLGetInteger(hDB,
    6914             :                               "SELECT COUNT(*) FROM gpkg_extensions WHERE "
    6915             :                               "extension_name IN ('related_tables', "
    6916             :                               "'gpkg_related_tables') AND "
    6917             :                               "lower(table_name) != 'gpkgext_relations'",
    6918           4 :                               &err) == 0)
    6919             :             {
    6920           2 :                 eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
    6921             :                                        "extension_name IN ('related_tables', "
    6922             :                                        "'gpkg_related_tables')");
    6923             :             }
    6924             : 
    6925           4 :             ClearCachedRelationships();
    6926             :         }
    6927             :     }
    6928             : 
    6929          42 :     if (eErr == OGRERR_NONE)
    6930             :     {
    6931          42 :         pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
    6932          42 :         eErr = SQLCommand(hDB, pszSQL);
    6933          42 :         sqlite3_free(pszSQL);
    6934             :     }
    6935             : 
    6936             :     // Check foreign key integrity
    6937          42 :     if (eErr == OGRERR_NONE)
    6938             :     {
    6939          42 :         eErr = PragmaCheck("foreign_key_check", "", 0);
    6940             :     }
    6941             : 
    6942          84 :     return eErr;
    6943             : }
    6944             : 
    6945             : /************************************************************************/
    6946             : /*                            DeleteLayer()                             */
    6947             : /************************************************************************/
    6948             : 
    6949          39 : OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
    6950             : {
    6951          77 :     if (!GetUpdate() || iLayer < 0 ||
    6952          38 :         iLayer >= static_cast<int>(m_apoLayers.size()))
    6953           2 :         return OGRERR_FAILURE;
    6954             : 
    6955          37 :     m_apoLayers[iLayer]->ResetReading();
    6956          37 :     m_apoLayers[iLayer]->SyncToDisk();
    6957             : 
    6958          74 :     CPLString osLayerName = m_apoLayers[iLayer]->GetName();
    6959             : 
    6960          37 :     CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
    6961             : 
    6962             :     // Temporary remove foreign key checks
    6963             :     const GPKGTemporaryForeignKeyCheckDisabler
    6964          37 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    6965             : 
    6966          37 :     OGRErr eErr = SoftStartTransaction();
    6967             : 
    6968          37 :     if (eErr == OGRERR_NONE)
    6969             :     {
    6970          37 :         if (m_apoLayers[iLayer]->HasSpatialIndex())
    6971          34 :             m_apoLayers[iLayer]->DropSpatialIndex();
    6972             : 
    6973             :         char *pszSQL =
    6974          37 :             sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
    6975             :                             "lower(table_name) = lower('%q')",
    6976             :                             osLayerName.c_str());
    6977          37 :         eErr = SQLCommand(hDB, pszSQL);
    6978          37 :         sqlite3_free(pszSQL);
    6979             :     }
    6980             : 
    6981          37 :     if (eErr == OGRERR_NONE && HasDataColumnsTable())
    6982             :     {
    6983           1 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
    6984             :                                        "lower(table_name) = lower('%q')",
    6985             :                                        osLayerName.c_str());
    6986           1 :         eErr = SQLCommand(hDB, pszSQL);
    6987           1 :         sqlite3_free(pszSQL);
    6988             :     }
    6989             : 
    6990             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    6991          37 :     if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
    6992             :     {
    6993          37 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
    6994             :                                        "lower(table_name) = lower('%q')",
    6995             :                                        osLayerName.c_str());
    6996          37 :         eErr = SQLCommand(hDB, pszSQL);
    6997          37 :         sqlite3_free(pszSQL);
    6998             :     }
    6999             : #endif
    7000             : 
    7001          37 :     if (eErr == OGRERR_NONE)
    7002             :     {
    7003          37 :         eErr = DeleteLayerCommon(osLayerName.c_str());
    7004             :     }
    7005             : 
    7006          37 :     if (eErr == OGRERR_NONE)
    7007             :     {
    7008          37 :         eErr = SoftCommitTransaction();
    7009          37 :         if (eErr == OGRERR_NONE)
    7010             :         {
    7011             :             /* Delete the layer object */
    7012          37 :             m_apoLayers.erase(m_apoLayers.begin() + iLayer);
    7013             :         }
    7014             :     }
    7015             :     else
    7016             :     {
    7017           0 :         SoftRollbackTransaction();
    7018             :     }
    7019             : 
    7020          37 :     return eErr;
    7021             : }
    7022             : 
    7023             : /************************************************************************/
    7024             : /*                         DeleteRasterLayer()                          */
    7025             : /************************************************************************/
    7026             : 
    7027           2 : OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
    7028             : {
    7029             :     // Temporary remove foreign key checks
    7030             :     const GPKGTemporaryForeignKeyCheckDisabler
    7031           2 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7032             : 
    7033           2 :     OGRErr eErr = SoftStartTransaction();
    7034             : 
    7035           2 :     if (eErr == OGRERR_NONE)
    7036             :     {
    7037           2 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix WHERE "
    7038             :                                        "lower(table_name) = lower('%q')",
    7039             :                                        pszLayerName);
    7040           2 :         eErr = SQLCommand(hDB, pszSQL);
    7041           2 :         sqlite3_free(pszSQL);
    7042             :     }
    7043             : 
    7044           2 :     if (eErr == OGRERR_NONE)
    7045             :     {
    7046           2 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
    7047             :                                        "lower(table_name) = lower('%q')",
    7048             :                                        pszLayerName);
    7049           2 :         eErr = SQLCommand(hDB, pszSQL);
    7050           2 :         sqlite3_free(pszSQL);
    7051             :     }
    7052             : 
    7053           2 :     if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
    7054             :     {
    7055             :         char *pszSQL =
    7056           1 :             sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
    7057             :                             "WHERE lower(tile_matrix_set_name) = lower('%q')",
    7058             :                             pszLayerName);
    7059           1 :         eErr = SQLCommand(hDB, pszSQL);
    7060           1 :         sqlite3_free(pszSQL);
    7061             : 
    7062           1 :         if (eErr == OGRERR_NONE)
    7063             :         {
    7064             :             pszSQL =
    7065           1 :                 sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
    7066             :                                 "WHERE lower(tpudt_name) = lower('%q')",
    7067             :                                 pszLayerName);
    7068           1 :             eErr = SQLCommand(hDB, pszSQL);
    7069           1 :             sqlite3_free(pszSQL);
    7070             :         }
    7071             :     }
    7072             : 
    7073           2 :     if (eErr == OGRERR_NONE)
    7074             :     {
    7075           2 :         eErr = DeleteLayerCommon(pszLayerName);
    7076             :     }
    7077             : 
    7078           2 :     if (eErr == OGRERR_NONE)
    7079             :     {
    7080           2 :         eErr = SoftCommitTransaction();
    7081             :     }
    7082             :     else
    7083             :     {
    7084           0 :         SoftRollbackTransaction();
    7085             :     }
    7086             : 
    7087           4 :     return eErr;
    7088             : }
    7089             : 
    7090             : /************************************************************************/
    7091             : /*                     DeleteVectorOrRasterLayer()                      */
    7092             : /************************************************************************/
    7093             : 
    7094          13 : bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
    7095             : {
    7096             : 
    7097          13 :     int idx = FindLayerIndex(pszLayerName);
    7098          13 :     if (idx >= 0)
    7099             :     {
    7100           5 :         DeleteLayer(idx);
    7101           5 :         return true;
    7102             :     }
    7103             : 
    7104             :     char *pszSQL =
    7105           8 :         sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
    7106             :                         "lower(table_name) = lower('%q') "
    7107             :                         "AND data_type IN ('tiles', '2d-gridded-coverage')",
    7108             :                         pszLayerName);
    7109           8 :     bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
    7110           8 :     sqlite3_free(pszSQL);
    7111           8 :     if (bIsRasterTable)
    7112             :     {
    7113           2 :         DeleteRasterLayer(pszLayerName);
    7114           2 :         return true;
    7115             :     }
    7116           6 :     return false;
    7117             : }
    7118             : 
    7119           7 : bool GDALGeoPackageDataset::RenameVectorOrRasterLayer(
    7120             :     const char *pszLayerName, const char *pszNewLayerName)
    7121             : {
    7122           7 :     int idx = FindLayerIndex(pszLayerName);
    7123           7 :     if (idx >= 0)
    7124             :     {
    7125           4 :         m_apoLayers[idx]->Rename(pszNewLayerName);
    7126           4 :         return true;
    7127             :     }
    7128             : 
    7129             :     char *pszSQL =
    7130           3 :         sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
    7131             :                         "lower(table_name) = lower('%q') "
    7132             :                         "AND data_type IN ('tiles', '2d-gridded-coverage')",
    7133             :                         pszLayerName);
    7134           3 :     const bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
    7135           3 :     sqlite3_free(pszSQL);
    7136             : 
    7137           3 :     if (bIsRasterTable)
    7138             :     {
    7139           2 :         return RenameRasterLayer(pszLayerName, pszNewLayerName);
    7140             :     }
    7141             : 
    7142           1 :     return false;
    7143             : }
    7144             : 
    7145           2 : bool GDALGeoPackageDataset::RenameRasterLayer(const char *pszLayerName,
    7146             :                                               const char *pszNewLayerName)
    7147             : {
    7148           4 :     std::string osSQL;
    7149             : 
    7150           2 :     char *pszSQL = sqlite3_mprintf(
    7151             :         "SELECT 1 FROM sqlite_master WHERE lower(name) = lower('%q') "
    7152             :         "AND type IN ('table', 'view')",
    7153             :         pszNewLayerName);
    7154           2 :     const bool bAlreadyExists = SQLGetInteger(GetDB(), pszSQL, nullptr) == 1;
    7155           2 :     sqlite3_free(pszSQL);
    7156           2 :     if (bAlreadyExists)
    7157             :     {
    7158           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists",
    7159             :                  pszNewLayerName);
    7160           0 :         return false;
    7161             :     }
    7162             : 
    7163             :     // Temporary remove foreign key checks
    7164             :     const GPKGTemporaryForeignKeyCheckDisabler
    7165           4 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7166             : 
    7167           2 :     if (SoftStartTransaction() != OGRERR_NONE)
    7168             :     {
    7169           0 :         return false;
    7170             :     }
    7171             : 
    7172           2 :     pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET table_name = '%q' WHERE "
    7173             :                              "lower(table_name) = lower('%q');",
    7174             :                              pszNewLayerName, pszLayerName);
    7175           2 :     osSQL = pszSQL;
    7176           2 :     sqlite3_free(pszSQL);
    7177             : 
    7178           2 :     pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' WHERE "
    7179             :                              "lower(identifier) = lower('%q');",
    7180             :                              pszNewLayerName, pszLayerName);
    7181           2 :     osSQL += pszSQL;
    7182           2 :     sqlite3_free(pszSQL);
    7183             : 
    7184             :     pszSQL =
    7185           2 :         sqlite3_mprintf("UPDATE gpkg_tile_matrix SET table_name = '%q' WHERE "
    7186             :                         "lower(table_name) = lower('%q');",
    7187             :                         pszNewLayerName, pszLayerName);
    7188           2 :     osSQL += pszSQL;
    7189           2 :     sqlite3_free(pszSQL);
    7190             : 
    7191           2 :     pszSQL = sqlite3_mprintf(
    7192             :         "UPDATE gpkg_tile_matrix_set SET table_name = '%q' WHERE "
    7193             :         "lower(table_name) = lower('%q');",
    7194             :         pszNewLayerName, pszLayerName);
    7195           2 :     osSQL += pszSQL;
    7196           2 :     sqlite3_free(pszSQL);
    7197             : 
    7198           2 :     if (HasGriddedCoverageAncillaryTable())
    7199             :     {
    7200           1 :         pszSQL = sqlite3_mprintf("UPDATE gpkg_2d_gridded_coverage_ancillary "
    7201             :                                  "SET tile_matrix_set_name = '%q' WHERE "
    7202             :                                  "lower(tile_matrix_set_name) = lower('%q');",
    7203             :                                  pszNewLayerName, pszLayerName);
    7204           1 :         osSQL += pszSQL;
    7205           1 :         sqlite3_free(pszSQL);
    7206             : 
    7207           1 :         pszSQL = sqlite3_mprintf(
    7208             :             "UPDATE gpkg_2d_gridded_tile_ancillary SET tpudt_name = '%q' WHERE "
    7209             :             "lower(tpudt_name) = lower('%q');",
    7210             :             pszNewLayerName, pszLayerName);
    7211           1 :         osSQL += pszSQL;
    7212           1 :         sqlite3_free(pszSQL);
    7213             :     }
    7214             : 
    7215           2 :     if (HasExtensionsTable())
    7216             :     {
    7217           2 :         pszSQL = sqlite3_mprintf(
    7218             :             "UPDATE gpkg_extensions SET table_name = '%q' WHERE "
    7219             :             "lower(table_name) = lower('%q');",
    7220             :             pszNewLayerName, pszLayerName);
    7221           2 :         osSQL += pszSQL;
    7222           2 :         sqlite3_free(pszSQL);
    7223             :     }
    7224             : 
    7225           2 :     if (HasMetadataTables())
    7226             :     {
    7227           1 :         pszSQL = sqlite3_mprintf(
    7228             :             "UPDATE gpkg_metadata_reference SET table_name = '%q' WHERE "
    7229             :             "lower(table_name) = lower('%q');",
    7230             :             pszNewLayerName, pszLayerName);
    7231           1 :         osSQL += pszSQL;
    7232           1 :         sqlite3_free(pszSQL);
    7233             :     }
    7234             : 
    7235           2 :     if (HasDataColumnsTable())
    7236             :     {
    7237           0 :         pszSQL = sqlite3_mprintf(
    7238             :             "UPDATE gpkg_data_columns SET table_name = '%q' WHERE "
    7239             :             "lower(table_name) = lower('%q');",
    7240             :             pszNewLayerName, pszLayerName);
    7241           0 :         osSQL += pszSQL;
    7242           0 :         sqlite3_free(pszSQL);
    7243             :     }
    7244             : 
    7245           2 :     if (HasQGISLayerStyles())
    7246             :     {
    7247             :         // Update QGIS styles
    7248             :         pszSQL =
    7249           0 :             sqlite3_mprintf("UPDATE layer_styles SET f_table_name = '%q' WHERE "
    7250             :                             "lower(f_table_name) = lower('%q');",
    7251             :                             pszNewLayerName, pszLayerName);
    7252           0 :         osSQL += pszSQL;
    7253           0 :         sqlite3_free(pszSQL);
    7254             :     }
    7255             : 
    7256             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7257           2 :     if (m_bHasGPKGOGRContents)
    7258             :     {
    7259           2 :         pszSQL = sqlite3_mprintf(
    7260             :             "UPDATE gpkg_ogr_contents SET table_name = '%q' WHERE "
    7261             :             "lower(table_name) = lower('%q');",
    7262             :             pszNewLayerName, pszLayerName);
    7263           2 :         osSQL += pszSQL;
    7264           2 :         sqlite3_free(pszSQL);
    7265             :     }
    7266             : #endif
    7267             : 
    7268           2 :     if (HasGpkgextRelationsTable())
    7269             :     {
    7270           0 :         pszSQL = sqlite3_mprintf(
    7271             :             "UPDATE gpkgext_relations SET base_table_name = '%q' WHERE "
    7272             :             "lower(base_table_name) = lower('%q');",
    7273             :             pszNewLayerName, pszLayerName);
    7274           0 :         osSQL += pszSQL;
    7275           0 :         sqlite3_free(pszSQL);
    7276             : 
    7277           0 :         pszSQL = sqlite3_mprintf(
    7278             :             "UPDATE gpkgext_relations SET related_table_name = '%q' WHERE "
    7279             :             "lower(related_table_name) = lower('%q');",
    7280             :             pszNewLayerName, pszLayerName);
    7281           0 :         osSQL += pszSQL;
    7282           0 :         sqlite3_free(pszSQL);
    7283             : 
    7284           0 :         pszSQL = sqlite3_mprintf(
    7285             :             "UPDATE gpkgext_relations SET mapping_table_name = '%q' WHERE "
    7286             :             "lower(mapping_table_name) = lower('%q');",
    7287             :             pszNewLayerName, pszLayerName);
    7288           0 :         osSQL += pszSQL;
    7289           0 :         sqlite3_free(pszSQL);
    7290             :     }
    7291             : 
    7292             :     // Drop all triggers for the layer
    7293           2 :     pszSQL = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type = "
    7294             :                              "'trigger' AND tbl_name = '%q'",
    7295             :                              pszLayerName);
    7296           2 :     auto oTriggerResult = SQLQuery(GetDB(), pszSQL);
    7297           2 :     sqlite3_free(pszSQL);
    7298           2 :     if (oTriggerResult)
    7299             :     {
    7300          14 :         for (int i = 0; i < oTriggerResult->RowCount(); i++)
    7301             :         {
    7302          12 :             const char *pszTriggerName = oTriggerResult->GetValue(0, i);
    7303          12 :             pszSQL = sqlite3_mprintf("DROP TRIGGER IF EXISTS \"%w\";",
    7304             :                                      pszTriggerName);
    7305          12 :             osSQL += pszSQL;
    7306          12 :             sqlite3_free(pszSQL);
    7307             :         }
    7308             :     }
    7309             : 
    7310           2 :     pszSQL = sqlite3_mprintf("ALTER TABLE \"%w\" RENAME TO \"%w\";",
    7311             :                              pszLayerName, pszNewLayerName);
    7312           2 :     osSQL += pszSQL;
    7313           2 :     sqlite3_free(pszSQL);
    7314             : 
    7315             :     // Recreate all zoom/tile triggers
    7316           2 :     if (oTriggerResult)
    7317             :     {
    7318           2 :         osSQL += CreateRasterTriggersSQL(pszNewLayerName);
    7319             :     }
    7320             : 
    7321           2 :     OGRErr eErr = SQLCommand(GetDB(), osSQL.c_str());
    7322             : 
    7323             :     // Check foreign key integrity
    7324           2 :     if (eErr == OGRERR_NONE)
    7325             :     {
    7326           2 :         eErr = PragmaCheck("foreign_key_check", "", 0);
    7327             :     }
    7328             : 
    7329           2 :     if (eErr == OGRERR_NONE)
    7330             :     {
    7331           2 :         eErr = SoftCommitTransaction();
    7332             :     }
    7333             :     else
    7334             :     {
    7335           0 :         SoftRollbackTransaction();
    7336             :     }
    7337             : 
    7338           2 :     return eErr == OGRERR_NONE;
    7339             : }
    7340             : 
    7341             : /************************************************************************/
    7342             : /*                           TestCapability()                           */
    7343             : /************************************************************************/
    7344             : 
    7345         531 : int GDALGeoPackageDataset::TestCapability(const char *pszCap) const
    7346             : {
    7347         531 :     if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
    7348         343 :         EQUAL(pszCap, "RenameLayer"))
    7349             :     {
    7350         188 :         return GetUpdate();
    7351             :     }
    7352         343 :     else if (EQUAL(pszCap, ODsCCurveGeometries))
    7353          12 :         return TRUE;
    7354         331 :     else if (EQUAL(pszCap, ODsCMeasuredGeometries))
    7355           8 :         return TRUE;
    7356         323 :     else if (EQUAL(pszCap, ODsCZGeometries))
    7357           8 :         return TRUE;
    7358         315 :     else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
    7359         315 :              EQUAL(pszCap, GDsCAddRelationship) ||
    7360         315 :              EQUAL(pszCap, GDsCDeleteRelationship) ||
    7361         315 :              EQUAL(pszCap, GDsCUpdateRelationship) ||
    7362         315 :              EQUAL(pszCap, ODsCAddFieldDomain) ||
    7363         313 :              EQUAL(pszCap, ODsCUpdateFieldDomain) ||
    7364         311 :              EQUAL(pszCap, ODsCDeleteFieldDomain))
    7365             :     {
    7366           6 :         return GetUpdate();
    7367             :     }
    7368             : 
    7369         309 :     return OGRSQLiteBaseDataSource::TestCapability(pszCap);
    7370             : }
    7371             : 
    7372             : /************************************************************************/
    7373             : /*                       ResetReadingAllLayers()                        */
    7374             : /************************************************************************/
    7375             : 
    7376         205 : void GDALGeoPackageDataset::ResetReadingAllLayers()
    7377             : {
    7378         415 :     for (auto &poLayer : m_apoLayers)
    7379             :     {
    7380         210 :         poLayer->ResetReading();
    7381             :     }
    7382         205 : }
    7383             : 
    7384             : /************************************************************************/
    7385             : /*                             ExecuteSQL()                             */
    7386             : /************************************************************************/
    7387             : 
    7388             : static const char *const apszFuncsWithSideEffects[] = {
    7389             :     "CreateSpatialIndex",
    7390             :     "DisableSpatialIndex",
    7391             :     "HasSpatialIndex",
    7392             :     "RegisterGeometryExtension",
    7393             : };
    7394             : 
    7395        5704 : OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
    7396             :                                             OGRGeometry *poSpatialFilter,
    7397             :                                             const char *pszDialect)
    7398             : 
    7399             : {
    7400        5704 :     m_bHasReadMetadataFromStorage = false;
    7401             : 
    7402        5704 :     FlushMetadata();
    7403             : 
    7404        5722 :     while (*pszSQLCommand != '\0' &&
    7405        5722 :            isspace(static_cast<unsigned char>(*pszSQLCommand)))
    7406          18 :         pszSQLCommand++;
    7407             : 
    7408       11408 :     CPLString osSQLCommand(pszSQLCommand);
    7409        5704 :     if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
    7410          48 :         osSQLCommand.pop_back();
    7411             : 
    7412       11407 :     if (osSQLCommand.ifind("AsGPB(ST_") != std::string::npos ||
    7413        5703 :         osSQLCommand.ifind("AsGPB( ST_") != std::string::npos)
    7414             :     {
    7415           1 :         CPLError(CE_Warning, CPLE_AppDefined,
    7416             :                  "Use of AsGPB(ST_xxx(...)) found in \"%s\". Since GDAL 3.13, "
    7417             :                  "ST_xxx() functions return a GeoPackage geometry when used "
    7418             :                  "with a GeoPackage connection, and the use of AsGPB() is no "
    7419             :                  "longer needed. It is here automatically removed",
    7420             :                  osSQLCommand.c_str());
    7421           1 :         osSQLCommand.replaceAll("AsGPB(ST_", "(ST_");
    7422           1 :         osSQLCommand.replaceAll("AsGPB( ST_", "(ST_");
    7423             :     }
    7424             : 
    7425        5704 :     if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
    7426             :     {
    7427             :         // Some SQL commands will influence the feature count behind our
    7428             :         // back, so disable it in that case.
    7429             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7430             :         const bool bInsertOrDelete =
    7431        5635 :             osSQLCommand.ifind("insert into ") != std::string::npos ||
    7432        2514 :             osSQLCommand.ifind("insert or replace into ") !=
    7433        8149 :                 std::string::npos ||
    7434        2477 :             osSQLCommand.ifind("delete from ") != std::string::npos;
    7435             :         const bool bRollback =
    7436        5635 :             osSQLCommand.ifind("rollback ") != std::string::npos;
    7437             : #endif
    7438             : 
    7439        7518 :         for (auto &poLayer : m_apoLayers)
    7440             :         {
    7441        1883 :             if (poLayer->SyncToDisk() != OGRERR_NONE)
    7442           0 :                 return nullptr;
    7443             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7444        2087 :             if (bRollback ||
    7445         204 :                 (bInsertOrDelete &&
    7446         204 :                  osSQLCommand.ifind(poLayer->GetName()) != std::string::npos))
    7447             :             {
    7448         202 :                 poLayer->DisableFeatureCount();
    7449             :             }
    7450             : #endif
    7451             :         }
    7452             :     }
    7453             : 
    7454        5704 :     if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
    7455        5703 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
    7456        5703 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
    7457        5703 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
    7458             :     {
    7459           1 :         OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
    7460             :     }
    7461        5703 :     else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
    7462        5702 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
    7463        5702 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
    7464        5702 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
    7465             :     {
    7466           1 :         OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
    7467             :     }
    7468             : 
    7469             :     /* -------------------------------------------------------------------- */
    7470             :     /*      DEBUG "SELECT nolock" command.                                  */
    7471             :     /* -------------------------------------------------------------------- */
    7472        5773 :     if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
    7473          69 :         EQUAL(osSQLCommand, "SELECT nolock"))
    7474             :     {
    7475           3 :         return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
    7476             :     }
    7477             : 
    7478             :     /* -------------------------------------------------------------------- */
    7479             :     /*      Special case DELLAYER: command.                                 */
    7480             :     /* -------------------------------------------------------------------- */
    7481        5701 :     if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
    7482             :     {
    7483           4 :         const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
    7484             : 
    7485           4 :         while (*pszLayerName == ' ')
    7486           0 :             pszLayerName++;
    7487             : 
    7488           4 :         if (!DeleteVectorOrRasterLayer(pszLayerName))
    7489             :         {
    7490           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
    7491             :                      pszLayerName);
    7492             :         }
    7493           4 :         return nullptr;
    7494             :     }
    7495             : 
    7496             :     /* -------------------------------------------------------------------- */
    7497             :     /*      Special case RECOMPUTE EXTENT ON command.                       */
    7498             :     /* -------------------------------------------------------------------- */
    7499        5697 :     if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
    7500             :     {
    7501             :         const char *pszLayerName =
    7502           4 :             osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
    7503             : 
    7504           4 :         while (*pszLayerName == ' ')
    7505           0 :             pszLayerName++;
    7506             : 
    7507           4 :         int idx = FindLayerIndex(pszLayerName);
    7508           4 :         if (idx >= 0)
    7509             :         {
    7510           4 :             m_apoLayers[idx]->RecomputeExtent();
    7511             :         }
    7512             :         else
    7513           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
    7514             :                      pszLayerName);
    7515           4 :         return nullptr;
    7516             :     }
    7517             : 
    7518             :     /* -------------------------------------------------------------------- */
    7519             :     /*      Intercept DROP TABLE                                            */
    7520             :     /* -------------------------------------------------------------------- */
    7521        5693 :     if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
    7522             :     {
    7523           9 :         const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
    7524             : 
    7525           9 :         while (*pszLayerName == ' ')
    7526           0 :             pszLayerName++;
    7527             : 
    7528           9 :         if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
    7529           4 :             return nullptr;
    7530             :     }
    7531             : 
    7532             :     /* -------------------------------------------------------------------- */
    7533             :     /*      Intercept ALTER TABLE src_table RENAME TO dst_table             */
    7534             :     /*      and       ALTER TABLE table RENAME COLUMN src_name TO dst_name  */
    7535             :     /*      and       ALTER TABLE table DROP COLUMN col_name                */
    7536             :     /*                                                                      */
    7537             :     /*      We do this because SQLite mechanisms can't deal with updating   */
    7538             :     /*      literal values in gpkg_ tables that refer to table and column   */
    7539             :     /*      names.                                                          */
    7540             :     /* -------------------------------------------------------------------- */
    7541        5689 :     if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
    7542             :     {
    7543           9 :         char **papszTokens = SQLTokenize(osSQLCommand);
    7544             :         /* ALTER TABLE src_table RENAME TO dst_table */
    7545          16 :         if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
    7546           7 :             EQUAL(papszTokens[4], "TO"))
    7547             :         {
    7548           7 :             const char *pszSrcTableName = papszTokens[2];
    7549           7 :             const char *pszDstTableName = papszTokens[5];
    7550           7 :             if (RenameVectorOrRasterLayer(SQLUnescape(pszSrcTableName),
    7551          14 :                                           SQLUnescape(pszDstTableName)))
    7552             :             {
    7553           6 :                 CSLDestroy(papszTokens);
    7554           6 :                 return nullptr;
    7555             :             }
    7556             :         }
    7557             :         /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
    7558           2 :         else if (CSLCount(papszTokens) == 8 &&
    7559           1 :                  EQUAL(papszTokens[3], "RENAME") &&
    7560           3 :                  EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
    7561             :         {
    7562           1 :             const char *pszTableName = papszTokens[2];
    7563           1 :             const char *pszSrcColumn = papszTokens[5];
    7564           1 :             const char *pszDstColumn = papszTokens[7];
    7565             :             OGRGeoPackageTableLayer *poLayer =
    7566           0 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7567           1 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7568           1 :             if (poLayer)
    7569             :             {
    7570           2 :                 int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
    7571           2 :                     SQLUnescape(pszSrcColumn));
    7572           1 :                 if (nSrcFieldIdx >= 0)
    7573             :                 {
    7574             :                     // OFTString or any type will do as we just alter the name
    7575             :                     // so it will be ignored.
    7576           1 :                     OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
    7577           1 :                                             OFTString);
    7578           1 :                     poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
    7579             :                                             ALTER_NAME_FLAG);
    7580           1 :                     CSLDestroy(papszTokens);
    7581           1 :                     return nullptr;
    7582             :                 }
    7583             :             }
    7584             :         }
    7585             :         /* ALTER TABLE table DROP COLUMN col_name */
    7586           2 :         else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
    7587           1 :                  EQUAL(papszTokens[4], "COLUMN"))
    7588             :         {
    7589           1 :             const char *pszTableName = papszTokens[2];
    7590           1 :             const char *pszColumnName = papszTokens[5];
    7591             :             OGRGeoPackageTableLayer *poLayer =
    7592           0 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7593           1 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7594           1 :             if (poLayer)
    7595             :             {
    7596           2 :                 int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
    7597           2 :                     SQLUnescape(pszColumnName));
    7598           1 :                 if (nFieldIdx >= 0)
    7599             :                 {
    7600           1 :                     poLayer->DeleteField(nFieldIdx);
    7601           1 :                     CSLDestroy(papszTokens);
    7602           1 :                     return nullptr;
    7603             :                 }
    7604             :             }
    7605             :         }
    7606           1 :         CSLDestroy(papszTokens);
    7607             :     }
    7608             : 
    7609        5681 :     if (ProcessTransactionSQL(osSQLCommand))
    7610             :     {
    7611         253 :         return nullptr;
    7612             :     }
    7613             : 
    7614        5428 :     if (EQUAL(osSQLCommand, "VACUUM"))
    7615             :     {
    7616          13 :         ResetReadingAllLayers();
    7617             :     }
    7618        5415 :     else if (STARTS_WITH_CI(osSQLCommand, "DELETE FROM "))
    7619             :     {
    7620             :         // Optimize truncation of a table, especially if it has a spatial
    7621             :         // index.
    7622          23 :         const CPLStringList aosTokens(SQLTokenize(osSQLCommand));
    7623          23 :         if (aosTokens.size() == 3)
    7624             :         {
    7625          16 :             const char *pszTableName = aosTokens[2];
    7626             :             OGRGeoPackageTableLayer *poLayer =
    7627           8 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7628          24 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7629          16 :             if (poLayer)
    7630             :             {
    7631           8 :                 poLayer->Truncate();
    7632           8 :                 return nullptr;
    7633             :             }
    7634             :         }
    7635             :     }
    7636        5392 :     else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
    7637           1 :         return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
    7638        5391 :     else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
    7639          67 :              !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
    7640          67 :              !EQUAL(pszDialect, "DEBUG"))
    7641           1 :         return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
    7642           1 :                                        pszDialect);
    7643             : 
    7644             :     /* -------------------------------------------------------------------- */
    7645             :     /*      Prepare statement.                                              */
    7646             :     /* -------------------------------------------------------------------- */
    7647        5418 :     sqlite3_stmt *hSQLStmt = nullptr;
    7648             : 
    7649             :     /* This will speed-up layer creation */
    7650             :     /* ORDER BY are costly to evaluate and are not necessary to establish */
    7651             :     /* the layer definition. */
    7652        5418 :     bool bUseStatementForGetNextFeature = true;
    7653        5418 :     bool bEmptyLayer = false;
    7654       10836 :     CPLString osSQLCommandTruncated(osSQLCommand);
    7655             : 
    7656       17970 :     if (osSQLCommand.ifind("SELECT ") == 0 &&
    7657        6276 :         CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
    7658         823 :             std::string::npos &&
    7659         823 :         osSQLCommand.ifind(" UNION ") == std::string::npos &&
    7660        7099 :         osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
    7661         823 :         osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
    7662             :     {
    7663         823 :         size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
    7664         823 :         if (nOrderByPos != std::string::npos)
    7665             :         {
    7666           9 :             osSQLCommandTruncated.resize(nOrderByPos);
    7667           9 :             bUseStatementForGetNextFeature = false;
    7668             :         }
    7669             :     }
    7670             : 
    7671        5418 :     int rc = prepareSql(hDB, osSQLCommandTruncated.c_str(),
    7672        5418 :                         static_cast<int>(osSQLCommandTruncated.size()),
    7673             :                         &hSQLStmt, nullptr);
    7674             : 
    7675        5418 :     if (rc != SQLITE_OK)
    7676             :     {
    7677           9 :         CPLError(CE_Failure, CPLE_AppDefined,
    7678             :                  "In ExecuteSQL(): sqlite3_prepare_v2(%s): %s",
    7679             :                  osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
    7680             : 
    7681           9 :         if (hSQLStmt != nullptr)
    7682             :         {
    7683           0 :             sqlite3_finalize(hSQLStmt);
    7684             :         }
    7685             : 
    7686           9 :         return nullptr;
    7687             :     }
    7688             : 
    7689             :     /* -------------------------------------------------------------------- */
    7690             :     /*      Do we get a resultset?                                          */
    7691             :     /* -------------------------------------------------------------------- */
    7692        5409 :     rc = sqlite3_step(hSQLStmt);
    7693             : 
    7694        7057 :     for (auto &poLayer : m_apoLayers)
    7695             :     {
    7696        1648 :         poLayer->RunDeferredDropRTreeTableIfNecessary();
    7697             :     }
    7698             : 
    7699        5409 :     if (rc != SQLITE_ROW)
    7700             :     {
    7701        4634 :         if (rc != SQLITE_DONE)
    7702             :         {
    7703           7 :             CPLError(CE_Failure, CPLE_AppDefined,
    7704             :                      "In ExecuteSQL(): sqlite3_step(%s):\n  %s",
    7705             :                      osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
    7706             : 
    7707           7 :             sqlite3_finalize(hSQLStmt);
    7708           7 :             return nullptr;
    7709             :         }
    7710             : 
    7711        4627 :         if (EQUAL(osSQLCommand, "VACUUM"))
    7712             :         {
    7713          13 :             sqlite3_finalize(hSQLStmt);
    7714             :             /* VACUUM rewrites the DB, so we need to reset the application id */
    7715          13 :             SetApplicationAndUserVersionId();
    7716          13 :             return nullptr;
    7717             :         }
    7718             : 
    7719        4614 :         if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
    7720             :         {
    7721        4487 :             sqlite3_finalize(hSQLStmt);
    7722        4487 :             return nullptr;
    7723             :         }
    7724             : 
    7725         127 :         bUseStatementForGetNextFeature = false;
    7726         127 :         bEmptyLayer = true;
    7727             :     }
    7728             : 
    7729             :     /* -------------------------------------------------------------------- */
    7730             :     /*      Special case for some functions which must be run               */
    7731             :     /*      only once                                                       */
    7732             :     /* -------------------------------------------------------------------- */
    7733         902 :     if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
    7734             :     {
    7735        4134 :         for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
    7736             :                                          sizeof(apszFuncsWithSideEffects[0]);
    7737             :              i++)
    7738             :         {
    7739        3333 :             if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
    7740             :                        strlen(apszFuncsWithSideEffects[i])))
    7741             :             {
    7742         112 :                 if (sqlite3_column_count(hSQLStmt) == 1 &&
    7743          56 :                     sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
    7744             :                 {
    7745          56 :                     int ret = sqlite3_column_int(hSQLStmt, 0);
    7746             : 
    7747          56 :                     sqlite3_finalize(hSQLStmt);
    7748             : 
    7749             :                     return new OGRSQLiteSingleFeatureLayer(
    7750          56 :                         apszFuncsWithSideEffects[i], ret);
    7751             :                 }
    7752             :             }
    7753             :         }
    7754             :     }
    7755          45 :     else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
    7756             :     {
    7757          63 :         if (sqlite3_column_count(hSQLStmt) == 1 &&
    7758          18 :             sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
    7759             :         {
    7760          15 :             int ret = sqlite3_column_int(hSQLStmt, 0);
    7761             : 
    7762          15 :             sqlite3_finalize(hSQLStmt);
    7763             : 
    7764          15 :             return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
    7765          15 :                                                    ret);
    7766             :         }
    7767          33 :         else if (sqlite3_column_count(hSQLStmt) == 1 &&
    7768           3 :                  sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
    7769             :         {
    7770             :             const char *pszRet = reinterpret_cast<const char *>(
    7771           3 :                 sqlite3_column_text(hSQLStmt, 0));
    7772             : 
    7773             :             OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
    7774           3 :                 osSQLCommand.c_str() + 7, pszRet);
    7775             : 
    7776           3 :             sqlite3_finalize(hSQLStmt);
    7777             : 
    7778           3 :             return poRet;
    7779             :         }
    7780             :     }
    7781             : 
    7782             :     /* -------------------------------------------------------------------- */
    7783             :     /*      Create layer.                                                   */
    7784             :     /* -------------------------------------------------------------------- */
    7785             : 
    7786             :     auto poLayer = std::make_unique<OGRGeoPackageSelectLayer>(
    7787             :         this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
    7788        1656 :         bEmptyLayer);
    7789             : 
    7790         831 :     if (poSpatialFilter != nullptr &&
    7791           3 :         poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
    7792           3 :         poLayer->SetSpatialFilter(0, poSpatialFilter);
    7793             : 
    7794         828 :     return poLayer.release();
    7795             : }
    7796             : 
    7797             : /************************************************************************/
    7798             : /*                          ReleaseResultSet()                          */
    7799             : /************************************************************************/
    7800             : 
    7801         861 : void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
    7802             : 
    7803             : {
    7804         861 :     delete poLayer;
    7805         861 : }
    7806             : 
    7807             : /************************************************************************/
    7808             : /*                         HasExtensionsTable()                         */
    7809             : /************************************************************************/
    7810             : 
    7811        7138 : bool GDALGeoPackageDataset::HasExtensionsTable()
    7812             : {
    7813        7138 :     return SQLGetInteger(
    7814             :                hDB,
    7815             :                "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
    7816             :                "AND type IN ('table', 'view')",
    7817        7138 :                nullptr) == 1;
    7818             : }
    7819             : 
    7820             : /************************************************************************/
    7821             : /*                       CheckUnknownExtensions()                       */
    7822             : /************************************************************************/
    7823             : 
    7824        1589 : void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
    7825             : {
    7826        1589 :     if (!HasExtensionsTable())
    7827         209 :         return;
    7828             : 
    7829        1380 :     char *pszSQL = nullptr;
    7830        1380 :     if (!bCheckRasterTable)
    7831        1166 :         pszSQL = sqlite3_mprintf(
    7832             :             "SELECT extension_name, definition, scope FROM gpkg_extensions "
    7833             :             "WHERE (table_name IS NULL "
    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             :             "'gdal_aspatial', "
    7839             :             "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
    7840             :             "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
    7841             :                                        // 17-066r1 finalization
    7842             :             "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
    7843             :             "'gpkg_metadata', "
    7844             :             "'gpkg_schema', "
    7845             :             "'gpkg_crs_wkt', "
    7846             :             "'gpkg_crs_wkt_1_1', "
    7847             :             "'related_tables', 'gpkg_related_tables')) "
    7848             : #ifdef WORKAROUND_SQLITE3_BUGS
    7849             :             "OR 0 "
    7850             : #endif
    7851             :             "LIMIT 1000");
    7852             :     else
    7853         214 :         pszSQL = sqlite3_mprintf(
    7854             :             "SELECT extension_name, definition, scope FROM gpkg_extensions "
    7855             :             "WHERE (lower(table_name) = lower('%q') "
    7856             :             "AND extension_name IS NOT NULL "
    7857             :             "AND definition IS NOT NULL "
    7858             :             "AND scope IS NOT NULL "
    7859             :             "AND extension_name NOT IN ("
    7860             :             "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
    7861             :             "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
    7862             :                                        // 17-066r1 finalization
    7863             :             "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
    7864             :             "'gpkg_metadata', "
    7865             :             "'gpkg_schema', "
    7866             :             "'gpkg_crs_wkt', "
    7867             :             "'gpkg_crs_wkt_1_1', "
    7868             :             "'related_tables', 'gpkg_related_tables')) "
    7869             : #ifdef WORKAROUND_SQLITE3_BUGS
    7870             :             "OR 0 "
    7871             : #endif
    7872             :             "LIMIT 1000",
    7873             :             m_osRasterTable.c_str());
    7874             : 
    7875        2760 :     auto oResultTable = SQLQuery(GetDB(), pszSQL);
    7876        1380 :     sqlite3_free(pszSQL);
    7877        1380 :     if (oResultTable && oResultTable->RowCount() > 0)
    7878             :     {
    7879          42 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    7880             :         {
    7881          21 :             const char *pszExtName = oResultTable->GetValue(0, i);
    7882          21 :             const char *pszDefinition = oResultTable->GetValue(1, i);
    7883          21 :             const char *pszScope = oResultTable->GetValue(2, i);
    7884          21 :             if (pszExtName == nullptr || pszDefinition == nullptr ||
    7885             :                 pszScope == nullptr)
    7886             :             {
    7887           0 :                 continue;
    7888             :             }
    7889             : 
    7890          21 :             if (EQUAL(pszExtName, "gpkg_webp"))
    7891             :             {
    7892          15 :                 if (GDALGetDriverByName("WEBP") == nullptr)
    7893             :                 {
    7894           1 :                     CPLError(
    7895             :                         CE_Warning, CPLE_AppDefined,
    7896             :                         "Table %s contains WEBP tiles, but GDAL configured "
    7897             :                         "without WEBP support. Data will be missing",
    7898             :                         m_osRasterTable.c_str());
    7899             :                 }
    7900          15 :                 m_eTF = GPKG_TF_WEBP;
    7901          15 :                 continue;
    7902             :             }
    7903           6 :             if (EQUAL(pszExtName, "gpkg_zoom_other"))
    7904             :             {
    7905           2 :                 m_bZoomOther = true;
    7906           2 :                 continue;
    7907             :             }
    7908             : 
    7909           4 :             if (GetUpdate() && EQUAL(pszScope, "write-only"))
    7910             :             {
    7911           1 :                 CPLError(
    7912             :                     CE_Warning, CPLE_AppDefined,
    7913             :                     "Database relies on the '%s' (%s) extension that should "
    7914             :                     "be implemented for safe write-support, but is not "
    7915             :                     "currently. "
    7916             :                     "Update of that database are strongly discouraged to avoid "
    7917             :                     "corruption.",
    7918             :                     pszExtName, pszDefinition);
    7919             :             }
    7920           3 :             else if (GetUpdate() && EQUAL(pszScope, "read-write"))
    7921             :             {
    7922           1 :                 CPLError(
    7923             :                     CE_Warning, CPLE_AppDefined,
    7924             :                     "Database relies on the '%s' (%s) extension that should "
    7925             :                     "be implemented in order to read/write it safely, but is "
    7926             :                     "not currently. "
    7927             :                     "Some data may be missing while reading that database, and "
    7928             :                     "updates are strongly discouraged.",
    7929             :                     pszExtName, pszDefinition);
    7930             :             }
    7931           2 :             else if (EQUAL(pszScope, "read-write") &&
    7932             :                      // None of the NGA extensions at
    7933             :                      // http://ngageoint.github.io/GeoPackage/docs/extensions/
    7934             :                      // affect read-only scenarios
    7935           1 :                      !STARTS_WITH(pszExtName, "nga_"))
    7936             :             {
    7937           1 :                 CPLError(
    7938             :                     CE_Warning, CPLE_AppDefined,
    7939             :                     "Database relies on the '%s' (%s) extension that should "
    7940             :                     "be implemented in order to read it safely, but is not "
    7941             :                     "currently. "
    7942             :                     "Some data may be missing while reading that database.",
    7943             :                     pszExtName, pszDefinition);
    7944             :             }
    7945             :         }
    7946             :     }
    7947             : }
    7948             : 
    7949             : /************************************************************************/
    7950             : /*                      HasGDALAspatialExtension()                      */
    7951             : /************************************************************************/
    7952             : 
    7953        1127 : bool GDALGeoPackageDataset::HasGDALAspatialExtension()
    7954             : {
    7955        1127 :     if (!HasExtensionsTable())
    7956         102 :         return false;
    7957             : 
    7958             :     auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
    7959             :                                       "WHERE (extension_name = 'gdal_aspatial' "
    7960             :                                       "AND table_name IS NULL "
    7961             :                                       "AND column_name IS NULL)"
    7962             : #ifdef WORKAROUND_SQLITE3_BUGS
    7963             :                                       " OR 0"
    7964             : #endif
    7965        1025 :     );
    7966        1025 :     bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
    7967        1025 :     return bHasExtension;
    7968             : }
    7969             : 
    7970             : std::string
    7971         192 : GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName)
    7972             : {
    7973             :     char *pszSQL;
    7974         192 :     std::string osSQL;
    7975             :     /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
    7976             :      * Definition SQL  */
    7977         192 :     pszSQL = sqlite3_mprintf(
    7978             :         "CREATE TRIGGER \"%w_zoom_insert\" "
    7979             :         "BEFORE INSERT ON \"%w\" "
    7980             :         "FOR EACH ROW BEGIN "
    7981             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    7982             :         "constraint: zoom_level not specified for table in "
    7983             :         "gpkg_tile_matrix') "
    7984             :         "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
    7985             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
    7986             :         "END; "
    7987             :         "CREATE TRIGGER \"%w_zoom_update\" "
    7988             :         "BEFORE UPDATE OF zoom_level ON \"%w\" "
    7989             :         "FOR EACH ROW BEGIN "
    7990             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    7991             :         "constraint: zoom_level not specified for table in "
    7992             :         "gpkg_tile_matrix') "
    7993             :         "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
    7994             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
    7995             :         "END; "
    7996             :         "CREATE TRIGGER \"%w_tile_column_insert\" "
    7997             :         "BEFORE INSERT ON \"%w\" "
    7998             :         "FOR EACH ROW BEGIN "
    7999             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8000             :         "constraint: tile_column cannot be < 0') "
    8001             :         "WHERE (NEW.tile_column < 0) ; "
    8002             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8003             :         "constraint: tile_column must by < matrix_width specified for "
    8004             :         "table and zoom level in gpkg_tile_matrix') "
    8005             :         "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
    8006             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8007             :         "zoom_level = NEW.zoom_level)); "
    8008             :         "END; "
    8009             :         "CREATE TRIGGER \"%w_tile_column_update\" "
    8010             :         "BEFORE UPDATE OF tile_column ON \"%w\" "
    8011             :         "FOR EACH ROW BEGIN "
    8012             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8013             :         "constraint: tile_column cannot be < 0') "
    8014             :         "WHERE (NEW.tile_column < 0) ; "
    8015             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8016             :         "constraint: tile_column must by < matrix_width specified for "
    8017             :         "table and zoom level in gpkg_tile_matrix') "
    8018             :         "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
    8019             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8020             :         "zoom_level = NEW.zoom_level)); "
    8021             :         "END; "
    8022             :         "CREATE TRIGGER \"%w_tile_row_insert\" "
    8023             :         "BEFORE INSERT ON \"%w\" "
    8024             :         "FOR EACH ROW BEGIN "
    8025             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8026             :         "constraint: tile_row cannot be < 0') "
    8027             :         "WHERE (NEW.tile_row < 0) ; "
    8028             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8029             :         "constraint: tile_row must by < matrix_height specified for "
    8030             :         "table and zoom level in gpkg_tile_matrix') "
    8031             :         "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
    8032             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8033             :         "zoom_level = NEW.zoom_level)); "
    8034             :         "END; "
    8035             :         "CREATE TRIGGER \"%w_tile_row_update\" "
    8036             :         "BEFORE UPDATE OF tile_row ON \"%w\" "
    8037             :         "FOR EACH ROW BEGIN "
    8038             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8039             :         "constraint: tile_row cannot be < 0') "
    8040             :         "WHERE (NEW.tile_row < 0) ; "
    8041             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8042             :         "constraint: tile_row must by < matrix_height specified for "
    8043             :         "table and zoom level in gpkg_tile_matrix') "
    8044             :         "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
    8045             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8046             :         "zoom_level = NEW.zoom_level)); "
    8047             :         "END; ",
    8048             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8049             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8050             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8051             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8052             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8053             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8054             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8055             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8056             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8057             :         osTableName.c_str());
    8058         192 :     osSQL = pszSQL;
    8059         192 :     sqlite3_free(pszSQL);
    8060         192 :     return osSQL;
    8061             : }
    8062             : 
    8063             : /************************************************************************/
    8064             : /*                  CreateExtensionsTableIfNecessary()                  */
    8065             : /************************************************************************/
    8066             : 
    8067        1273 : OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
    8068             : {
    8069             :     /* Check if the table gpkg_extensions exists */
    8070        1273 :     if (HasExtensionsTable())
    8071         423 :         return OGRERR_NONE;
    8072             : 
    8073             :     /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
    8074             :     /* in a corresponding row in the gpkg_extensions table. The absence of a */
    8075             :     /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
    8076             :     /* SHALL both indicate the absence of extensions to a GeoPackage. */
    8077         850 :     const char *pszCreateGpkgExtensions =
    8078             :         "CREATE TABLE gpkg_extensions ("
    8079             :         "table_name TEXT,"
    8080             :         "column_name TEXT,"
    8081             :         "extension_name TEXT NOT NULL,"
    8082             :         "definition TEXT NOT NULL,"
    8083             :         "scope TEXT NOT NULL,"
    8084             :         "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
    8085             :         ")";
    8086             : 
    8087         850 :     return SQLCommand(hDB, pszCreateGpkgExtensions);
    8088             : }
    8089             : 
    8090             : /************************************************************************/
    8091             : /*                 OGR_GPKG_Intersects_Spatial_Filter()                 */
    8092             : /************************************************************************/
    8093             : 
    8094       23135 : void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
    8095             :                                         sqlite3_value **argv)
    8096             : {
    8097       23135 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8098             :     {
    8099           0 :         sqlite3_result_int(pContext, 0);
    8100       23125 :         return;
    8101             :     }
    8102             : 
    8103             :     auto poLayer =
    8104       23135 :         static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
    8105             : 
    8106       23135 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8107             :     const GByte *pabyBLOB =
    8108       23135 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8109             : 
    8110             :     GPkgHeader sHeader;
    8111       46270 :     if (poLayer->m_bFilterIsEnvelope &&
    8112       23135 :         OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
    8113             :     {
    8114       23135 :         if (sHeader.bExtentHasXY)
    8115             :         {
    8116          95 :             OGREnvelope sEnvelope;
    8117          95 :             sEnvelope.MinX = sHeader.MinX;
    8118          95 :             sEnvelope.MinY = sHeader.MinY;
    8119          95 :             sEnvelope.MaxX = sHeader.MaxX;
    8120          95 :             sEnvelope.MaxY = sHeader.MaxY;
    8121          95 :             if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
    8122             :             {
    8123          31 :                 sqlite3_result_int(pContext, 1);
    8124          31 :                 return;
    8125             :             }
    8126             :         }
    8127             : 
    8128             :         // Check if at least one point falls into the layer filter envelope
    8129             :         // nHeaderLen is > 0 for GeoPackage geometries
    8130       46208 :         if (sHeader.nHeaderLen > 0 &&
    8131       23104 :             OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
    8132       23104 :                                         nBLOBLen - sHeader.nHeaderLen,
    8133       23104 :                                         poLayer->m_sFilterEnvelope))
    8134             :         {
    8135       23094 :             sqlite3_result_int(pContext, 1);
    8136       23094 :             return;
    8137             :         }
    8138             :     }
    8139             : 
    8140             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8141          10 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8142          10 :     if (poGeom == nullptr)
    8143             :     {
    8144             :         // Try also spatialite geometry blobs
    8145           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8146           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8147           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8148             :         {
    8149           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8150           0 :             sqlite3_result_int(pContext, 0);
    8151           0 :             return;
    8152             :         }
    8153           0 :         poGeom.reset(poGeomSpatialite);
    8154             :     }
    8155             : 
    8156          10 :     sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
    8157             : }
    8158             : 
    8159             : /************************************************************************/
    8160             : /*                        OGRGeoPackageSTMinX()                         */
    8161             : /************************************************************************/
    8162             : 
    8163      252845 : static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
    8164             :                                 sqlite3_value **argv)
    8165             : {
    8166             :     GPkgHeader sHeader;
    8167      252845 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8168             :     {
    8169          17 :         sqlite3_result_null(pContext);
    8170          17 :         return;
    8171             :     }
    8172      252828 :     sqlite3_result_double(pContext, sHeader.MinX);
    8173             : }
    8174             : 
    8175             : /************************************************************************/
    8176             : /*                        OGRGeoPackageSTMinY()                         */
    8177             : /************************************************************************/
    8178             : 
    8179      252829 : static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
    8180             :                                 sqlite3_value **argv)
    8181             : {
    8182             :     GPkgHeader sHeader;
    8183      252829 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8184             :     {
    8185           1 :         sqlite3_result_null(pContext);
    8186           1 :         return;
    8187             :     }
    8188      252828 :     sqlite3_result_double(pContext, sHeader.MinY);
    8189             : }
    8190             : 
    8191             : /************************************************************************/
    8192             : /*                        OGRGeoPackageSTMaxX()                         */
    8193             : /************************************************************************/
    8194             : 
    8195      252829 : static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
    8196             :                                 sqlite3_value **argv)
    8197             : {
    8198             :     GPkgHeader sHeader;
    8199      252829 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8200             :     {
    8201           1 :         sqlite3_result_null(pContext);
    8202           1 :         return;
    8203             :     }
    8204      252828 :     sqlite3_result_double(pContext, sHeader.MaxX);
    8205             : }
    8206             : 
    8207             : /************************************************************************/
    8208             : /*                        OGRGeoPackageSTMaxY()                         */
    8209             : /************************************************************************/
    8210             : 
    8211      252829 : static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
    8212             :                                 sqlite3_value **argv)
    8213             : {
    8214             :     GPkgHeader sHeader;
    8215      252829 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8216             :     {
    8217           1 :         sqlite3_result_null(pContext);
    8218           1 :         return;
    8219             :     }
    8220      252828 :     sqlite3_result_double(pContext, sHeader.MaxY);
    8221             : }
    8222             : 
    8223             : /************************************************************************/
    8224             : /*                       OGRGeoPackageSTIsEmpty()                       */
    8225             : /************************************************************************/
    8226             : 
    8227      254255 : static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
    8228             :                                    sqlite3_value **argv)
    8229             : {
    8230             :     GPkgHeader sHeader;
    8231      254255 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8232             :     {
    8233           2 :         sqlite3_result_null(pContext);
    8234           2 :         return;
    8235             :     }
    8236      254253 :     sqlite3_result_int(pContext, sHeader.bEmpty);
    8237             : }
    8238             : 
    8239             : /************************************************************************/
    8240             : /*                    OGRGeoPackageSTGeometryType()                     */
    8241             : /************************************************************************/
    8242             : 
    8243           7 : static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
    8244             :                                         sqlite3_value **argv)
    8245             : {
    8246             :     GPkgHeader sHeader;
    8247             : 
    8248           7 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8249             :     const GByte *pabyBLOB =
    8250           7 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8251             :     OGRwkbGeometryType eGeometryType;
    8252             : 
    8253          13 :     if (nBLOBLen < 8 ||
    8254           6 :         GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
    8255             :     {
    8256           2 :         if (OGRSQLiteGetSpatialiteGeometryHeader(
    8257             :                 pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
    8258           2 :                 nullptr, nullptr, nullptr) == OGRERR_NONE)
    8259             :         {
    8260           1 :             sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
    8261             :                                 SQLITE_TRANSIENT);
    8262           4 :             return;
    8263             :         }
    8264             :         else
    8265             :         {
    8266           1 :             sqlite3_result_null(pContext);
    8267           1 :             return;
    8268             :         }
    8269             :     }
    8270             : 
    8271           5 :     if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
    8272             :     {
    8273           2 :         sqlite3_result_null(pContext);
    8274           2 :         return;
    8275             :     }
    8276             : 
    8277           3 :     OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
    8278             :                                         wkbVariantIso, &eGeometryType);
    8279           3 :     if (err != OGRERR_NONE)
    8280           1 :         sqlite3_result_null(pContext);
    8281             :     else
    8282           2 :         sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
    8283             :                             SQLITE_TRANSIENT);
    8284             : }
    8285             : 
    8286             : /************************************************************************/
    8287             : /*                 OGRGeoPackageSTEnvelopesIntersects()                 */
    8288             : /************************************************************************/
    8289             : 
    8290         118 : static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
    8291             :                                                int argc, sqlite3_value **argv)
    8292             : {
    8293             :     GPkgHeader sHeader;
    8294         118 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8295             :     {
    8296           2 :         sqlite3_result_int(pContext, FALSE);
    8297         107 :         return;
    8298             :     }
    8299         116 :     const double dfMinX = sqlite3_value_double(argv[1]);
    8300         116 :     if (sHeader.MaxX < dfMinX)
    8301             :     {
    8302          93 :         sqlite3_result_int(pContext, FALSE);
    8303          93 :         return;
    8304             :     }
    8305          23 :     const double dfMinY = sqlite3_value_double(argv[2]);
    8306          23 :     if (sHeader.MaxY < dfMinY)
    8307             :     {
    8308          11 :         sqlite3_result_int(pContext, FALSE);
    8309          11 :         return;
    8310             :     }
    8311          12 :     const double dfMaxX = sqlite3_value_double(argv[3]);
    8312          12 :     if (sHeader.MinX > dfMaxX)
    8313             :     {
    8314           1 :         sqlite3_result_int(pContext, FALSE);
    8315           1 :         return;
    8316             :     }
    8317          11 :     const double dfMaxY = sqlite3_value_double(argv[4]);
    8318          11 :     sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
    8319             : }
    8320             : 
    8321             : /************************************************************************/
    8322             : /*            OGRGeoPackageSTEnvelopesIntersectsTwoParams()             */
    8323             : /************************************************************************/
    8324             : 
    8325             : static void
    8326           3 : OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
    8327             :                                             sqlite3_value **argv)
    8328             : {
    8329             :     GPkgHeader sHeader;
    8330           3 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
    8331             :     {
    8332           0 :         sqlite3_result_int(pContext, FALSE);
    8333           2 :         return;
    8334             :     }
    8335             :     GPkgHeader sHeader2;
    8336           3 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
    8337             :                                 1))
    8338             :     {
    8339           0 :         sqlite3_result_int(pContext, FALSE);
    8340           0 :         return;
    8341             :     }
    8342           3 :     if (sHeader.MaxX < sHeader2.MinX)
    8343             :     {
    8344           1 :         sqlite3_result_int(pContext, FALSE);
    8345           1 :         return;
    8346             :     }
    8347           2 :     if (sHeader.MaxY < sHeader2.MinY)
    8348             :     {
    8349           0 :         sqlite3_result_int(pContext, FALSE);
    8350           0 :         return;
    8351             :     }
    8352           2 :     if (sHeader.MinX > sHeader2.MaxX)
    8353             :     {
    8354           1 :         sqlite3_result_int(pContext, FALSE);
    8355           1 :         return;
    8356             :     }
    8357           1 :     sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
    8358             : }
    8359             : 
    8360             : /************************************************************************/
    8361             : /*                   OGRGeoPackageGPKGIsAssignable()                    */
    8362             : /************************************************************************/
    8363             : 
    8364           8 : static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
    8365             :                                           int /*argc*/, sqlite3_value **argv)
    8366             : {
    8367          15 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8368           7 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    8369             :     {
    8370           2 :         sqlite3_result_int(pContext, 0);
    8371           2 :         return;
    8372             :     }
    8373             : 
    8374             :     const char *pszExpected =
    8375           6 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8376             :     const char *pszActual =
    8377           6 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8378           6 :     int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
    8379             :                                             OGRFromOGCGeomType(pszExpected));
    8380           6 :     sqlite3_result_int(pContext, bIsAssignable);
    8381             : }
    8382             : 
    8383             : /************************************************************************/
    8384             : /*                        OGRGeoPackageSTSRID()                         */
    8385             : /************************************************************************/
    8386             : 
    8387          12 : static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
    8388             :                                 sqlite3_value **argv)
    8389             : {
    8390             :     GPkgHeader sHeader;
    8391          12 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8392             :     {
    8393           2 :         sqlite3_result_null(pContext);
    8394           2 :         return;
    8395             :     }
    8396          10 :     sqlite3_result_int(pContext, sHeader.iSrsId);
    8397             : }
    8398             : 
    8399             : /************************************************************************/
    8400             : /*                        OGRGeoPackageSetSRID()                        */
    8401             : /************************************************************************/
    8402             : 
    8403          28 : static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
    8404             :                                  sqlite3_value **argv)
    8405             : {
    8406          28 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8407             :     {
    8408           1 :         sqlite3_result_null(pContext);
    8409           1 :         return;
    8410             :     }
    8411          27 :     const int nDestSRID = sqlite3_value_int(argv[1]);
    8412             :     GPkgHeader sHeader;
    8413          27 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8414             :     const GByte *pabyBLOB =
    8415          27 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8416             : 
    8417          54 :     if (nBLOBLen < 8 ||
    8418          27 :         GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
    8419             :     {
    8420             :         // Try also spatialite geometry blobs
    8421           0 :         OGRGeometry *poGeom = nullptr;
    8422           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
    8423             :             OGRERR_NONE)
    8424             :         {
    8425           0 :             sqlite3_result_null(pContext);
    8426           0 :             return;
    8427             :         }
    8428           0 :         size_t nBLOBDestLen = 0;
    8429             :         GByte *pabyDestBLOB =
    8430           0 :             GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
    8431           0 :         if (!pabyDestBLOB)
    8432             :         {
    8433           0 :             sqlite3_result_null(pContext);
    8434           0 :             return;
    8435             :         }
    8436           0 :         sqlite3_result_blob(pContext, pabyDestBLOB,
    8437             :                             static_cast<int>(nBLOBDestLen), VSIFree);
    8438           0 :         return;
    8439             :     }
    8440             : 
    8441          27 :     GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
    8442          27 :     memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
    8443          27 :     int32_t nSRIDToSerialize = nDestSRID;
    8444          27 :     if (OGR_SWAP(sHeader.eByteOrder))
    8445           0 :         nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
    8446          27 :     memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
    8447          27 :     sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
    8448             : }
    8449             : 
    8450             : /************************************************************************/
    8451             : /*                      OGRGeoPackageSTMakeValid()                      */
    8452             : /************************************************************************/
    8453             : 
    8454           3 : static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
    8455             :                                      sqlite3_value **argv)
    8456             : {
    8457           3 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8458             :     {
    8459           2 :         sqlite3_result_null(pContext);
    8460           2 :         return;
    8461             :     }
    8462           1 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8463             :     const GByte *pabyBLOB =
    8464           1 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8465             : 
    8466             :     GPkgHeader sHeader;
    8467           1 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8468             :     {
    8469           0 :         sqlite3_result_null(pContext);
    8470           0 :         return;
    8471             :     }
    8472             : 
    8473             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8474           1 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8475           1 :     if (poGeom == nullptr)
    8476             :     {
    8477             :         // Try also spatialite geometry blobs
    8478           0 :         OGRGeometry *poGeomPtr = nullptr;
    8479           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
    8480             :             OGRERR_NONE)
    8481             :         {
    8482           0 :             sqlite3_result_null(pContext);
    8483           0 :             return;
    8484             :         }
    8485           0 :         poGeom.reset(poGeomPtr);
    8486             :     }
    8487           1 :     auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
    8488           1 :     if (poValid == nullptr)
    8489             :     {
    8490           0 :         sqlite3_result_null(pContext);
    8491           0 :         return;
    8492             :     }
    8493             : 
    8494           1 :     size_t nBLOBDestLen = 0;
    8495           1 :     GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
    8496             :                                               nullptr, &nBLOBDestLen);
    8497           1 :     if (!pabyDestBLOB)
    8498             :     {
    8499           0 :         sqlite3_result_null(pContext);
    8500           0 :         return;
    8501             :     }
    8502           1 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    8503             :                         VSIFree);
    8504             : }
    8505             : 
    8506             : /************************************************************************/
    8507             : /*                        OGRGeoPackageSTArea()                         */
    8508             : /************************************************************************/
    8509             : 
    8510          19 : static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
    8511             :                                 sqlite3_value **argv)
    8512             : {
    8513          19 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8514             :     {
    8515           1 :         sqlite3_result_null(pContext);
    8516          15 :         return;
    8517             :     }
    8518          18 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8519             :     const GByte *pabyBLOB =
    8520          18 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8521             : 
    8522             :     GPkgHeader sHeader;
    8523           0 :     std::unique_ptr<OGRGeometry> poGeom;
    8524          18 :     if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
    8525             :     {
    8526          16 :         if (sHeader.bEmpty)
    8527             :         {
    8528           3 :             sqlite3_result_double(pContext, 0);
    8529          13 :             return;
    8530             :         }
    8531          13 :         const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
    8532          13 :         size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
    8533             :         bool bNeedSwap;
    8534             :         uint32_t nType;
    8535          13 :         if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
    8536             :         {
    8537          13 :             if (nType == wkbPolygon || nType == wkbPolygon25D ||
    8538          11 :                 nType == wkbPolygon + 1000 ||  // wkbPolygonZ
    8539          10 :                 nType == wkbPolygonM || nType == wkbPolygonZM)
    8540             :             {
    8541             :                 double dfArea;
    8542           5 :                 if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
    8543             :                 {
    8544           5 :                     sqlite3_result_double(pContext, dfArea);
    8545           5 :                     return;
    8546           0 :                 }
    8547             :             }
    8548           8 :             else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
    8549           6 :                      nType == wkbMultiPolygon + 1000 ||  // wkbMultiPolygonZ
    8550           5 :                      nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
    8551             :             {
    8552             :                 double dfArea;
    8553           5 :                 if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
    8554             :                 {
    8555           5 :                     sqlite3_result_double(pContext, dfArea);
    8556           5 :                     return;
    8557             :                 }
    8558             :             }
    8559             :         }
    8560             : 
    8561             :         // For curve geometries, fallback to OGRGeometry methods
    8562           3 :         poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8563             :     }
    8564             :     else
    8565             :     {
    8566             :         // Try also spatialite geometry blobs
    8567           2 :         OGRGeometry *poGeomPtr = nullptr;
    8568           2 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
    8569             :             OGRERR_NONE)
    8570             :         {
    8571           1 :             sqlite3_result_null(pContext);
    8572           1 :             return;
    8573             :         }
    8574           1 :         poGeom.reset(poGeomPtr);
    8575             :     }
    8576           4 :     auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
    8577           4 :     if (poSurface == nullptr)
    8578             :     {
    8579           2 :         auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
    8580           2 :         if (poMultiSurface == nullptr)
    8581             :         {
    8582           1 :             sqlite3_result_double(pContext, 0);
    8583             :         }
    8584             :         else
    8585             :         {
    8586           1 :             sqlite3_result_double(pContext, poMultiSurface->get_Area());
    8587             :         }
    8588             :     }
    8589             :     else
    8590             :     {
    8591           2 :         sqlite3_result_double(pContext, poSurface->get_Area());
    8592             :     }
    8593             : }
    8594             : 
    8595             : /************************************************************************/
    8596             : /*                     OGRGeoPackageGeodesicArea()                      */
    8597             : /************************************************************************/
    8598             : 
    8599           5 : static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
    8600             :                                       sqlite3_value **argv)
    8601             : {
    8602           5 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8603             :     {
    8604           1 :         sqlite3_result_null(pContext);
    8605           3 :         return;
    8606             :     }
    8607           4 :     if (sqlite3_value_int(argv[1]) != 1)
    8608             :     {
    8609           2 :         CPLError(CE_Warning, CPLE_NotSupported,
    8610             :                  "ST_Area(geom, use_ellipsoid) is only supported for "
    8611             :                  "use_ellipsoid = 1");
    8612             :     }
    8613             : 
    8614           4 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8615             :     const GByte *pabyBLOB =
    8616           4 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8617             :     GPkgHeader sHeader;
    8618           4 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8619             :     {
    8620           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8621           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8622           1 :         return;
    8623             :     }
    8624             : 
    8625             :     GDALGeoPackageDataset *poDS =
    8626           3 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8627             : 
    8628             :     std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS(
    8629           3 :         poDS->GetSpatialRef(sHeader.iSrsId, true));
    8630           3 :     if (poSrcSRS == nullptr)
    8631             :     {
    8632           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    8633             :                  "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8634           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8635           1 :         return;
    8636             :     }
    8637             : 
    8638             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8639           2 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8640           2 :     if (poGeom == nullptr)
    8641             :     {
    8642             :         // Try also spatialite geometry blobs
    8643           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8644           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8645           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8646             :         {
    8647           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8648           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8649           0 :             return;
    8650             :         }
    8651           0 :         poGeom.reset(poGeomSpatialite);
    8652             :     }
    8653             : 
    8654           2 :     poGeom->assignSpatialReference(poSrcSRS.get());
    8655           2 :     sqlite3_result_double(
    8656             :         pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
    8657             : }
    8658             : 
    8659             : /************************************************************************/
    8660             : /*                OGRGeoPackageLengthOrGeodesicLength()                 */
    8661             : /************************************************************************/
    8662             : 
    8663           8 : static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext,
    8664             :                                                 int argc, sqlite3_value **argv)
    8665             : {
    8666           8 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8667             :     {
    8668           2 :         sqlite3_result_null(pContext);
    8669           5 :         return;
    8670             :     }
    8671           6 :     if (argc == 2 && sqlite3_value_int(argv[1]) != 1)
    8672             :     {
    8673           2 :         CPLError(CE_Warning, CPLE_NotSupported,
    8674             :                  "ST_Length(geom, use_ellipsoid) is only supported for "
    8675             :                  "use_ellipsoid = 1");
    8676             :     }
    8677             : 
    8678           6 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8679             :     const GByte *pabyBLOB =
    8680           6 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8681             :     GPkgHeader sHeader;
    8682           6 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8683             :     {
    8684           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8685           2 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8686           2 :         return;
    8687             :     }
    8688             : 
    8689             :     GDALGeoPackageDataset *poDS =
    8690           4 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8691             : 
    8692           0 :     std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS;
    8693           4 :     if (argc == 2)
    8694             :     {
    8695           3 :         poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
    8696           3 :         if (!poSrcSRS)
    8697             :         {
    8698           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    8699             :                      "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8700           1 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8701           1 :             return;
    8702             :         }
    8703             :     }
    8704             : 
    8705             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8706           3 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8707           3 :     if (poGeom == nullptr)
    8708             :     {
    8709             :         // Try also spatialite geometry blobs
    8710           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8711           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8712           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8713             :         {
    8714           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8715           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8716           0 :             return;
    8717             :         }
    8718           0 :         poGeom.reset(poGeomSpatialite);
    8719             :     }
    8720             : 
    8721           3 :     if (argc == 2)
    8722           2 :         poGeom->assignSpatialReference(poSrcSRS.get());
    8723             : 
    8724           6 :     sqlite3_result_double(
    8725             :         pContext,
    8726           1 :         argc == 1 ? OGR_G_Length(OGRGeometry::ToHandle(poGeom.get()))
    8727           2 :                   : OGR_G_GeodesicLength(OGRGeometry::ToHandle(poGeom.get())));
    8728             : }
    8729             : 
    8730             : /************************************************************************/
    8731             : /*                       OGRGeoPackageTransform()                       */
    8732             : /************************************************************************/
    8733             : 
    8734             : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
    8735             :                             sqlite3_value **argv);
    8736             : 
    8737          32 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
    8738             :                             sqlite3_value **argv)
    8739             : {
    8740          63 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
    8741          31 :         sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
    8742             :     {
    8743           2 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8744          32 :         return;
    8745             :     }
    8746             : 
    8747          30 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8748             :     const GByte *pabyBLOB =
    8749          30 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8750             :     GPkgHeader sHeader;
    8751          30 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8752             :     {
    8753           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8754           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8755           1 :         return;
    8756             :     }
    8757             : 
    8758          29 :     const int nDestSRID = sqlite3_value_int(argv[1]);
    8759          29 :     if (sHeader.iSrsId == nDestSRID)
    8760             :     {
    8761             :         // Return blob unmodified
    8762           3 :         sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
    8763           3 :         return;
    8764             :     }
    8765             : 
    8766             :     GDALGeoPackageDataset *poDS =
    8767          26 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8768             : 
    8769             :     // Try to get the cached coordinate transformation
    8770             :     OGRCoordinateTransformation *poCT;
    8771          26 :     if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
    8772          20 :         poDS->m_nLastCachedCTDstSRId == nDestSRID)
    8773             :     {
    8774          20 :         poCT = poDS->m_poLastCachedCT.get();
    8775             :     }
    8776             :     else
    8777             :     {
    8778             :         std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
    8779           6 :             poSrcSRS(poDS->GetSpatialRef(sHeader.iSrsId, true));
    8780           6 :         if (poSrcSRS == nullptr)
    8781             :         {
    8782           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    8783             :                      "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8784           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8785           0 :             return;
    8786             :         }
    8787             : 
    8788             :         std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
    8789           6 :             poDstSRS(poDS->GetSpatialRef(nDestSRID, true));
    8790           6 :         if (poDstSRS == nullptr)
    8791             :         {
    8792           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
    8793             :                      nDestSRID);
    8794           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8795           0 :             return;
    8796             :         }
    8797             :         poCT =
    8798           6 :             OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get());
    8799           6 :         if (poCT == nullptr)
    8800             :         {
    8801           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8802           0 :             return;
    8803             :         }
    8804             : 
    8805             :         // Cache coordinate transformation for potential later reuse
    8806           6 :         poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
    8807           6 :         poDS->m_nLastCachedCTDstSRId = nDestSRID;
    8808           6 :         poDS->m_poLastCachedCT.reset(poCT);
    8809           6 :         poCT = poDS->m_poLastCachedCT.get();
    8810             :     }
    8811             : 
    8812          26 :     if (sHeader.nHeaderLen >= 8)
    8813             :     {
    8814          26 :         std::vector<GByte> &abyNewBLOB = poDS->m_abyWKBTransformCache;
    8815          26 :         abyNewBLOB.resize(nBLOBLen);
    8816          26 :         memcpy(abyNewBLOB.data(), pabyBLOB, nBLOBLen);
    8817             : 
    8818          26 :         OGREnvelope3D oEnv3d;
    8819          26 :         if (!OGRWKBTransform(abyNewBLOB.data() + sHeader.nHeaderLen,
    8820          26 :                              nBLOBLen - sHeader.nHeaderLen, poCT,
    8821          78 :                              poDS->m_oWKBTransformCache, oEnv3d) ||
    8822          26 :             !GPkgUpdateHeader(abyNewBLOB.data(), nBLOBLen, nDestSRID,
    8823             :                               oEnv3d.MinX, oEnv3d.MaxX, oEnv3d.MinY,
    8824             :                               oEnv3d.MaxY, oEnv3d.MinZ, oEnv3d.MaxZ))
    8825             :         {
    8826           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8827           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8828           0 :             return;
    8829             :         }
    8830             : 
    8831          26 :         sqlite3_result_blob(pContext, abyNewBLOB.data(), nBLOBLen,
    8832             :                             SQLITE_TRANSIENT);
    8833          26 :         return;
    8834             :     }
    8835             : 
    8836             :     // Try also spatialite geometry blobs
    8837           0 :     OGRGeometry *poGeomSpatialite = nullptr;
    8838           0 :     if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8839           0 :                                           &poGeomSpatialite) != OGRERR_NONE)
    8840             :     {
    8841           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8842           0 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8843           0 :         return;
    8844             :     }
    8845           0 :     auto poGeom = std::unique_ptr<OGRGeometry>(poGeomSpatialite);
    8846             : 
    8847           0 :     if (poGeom->transform(poCT) != OGRERR_NONE)
    8848             :     {
    8849           0 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8850           0 :         return;
    8851             :     }
    8852             : 
    8853           0 :     size_t nBLOBDestLen = 0;
    8854             :     GByte *pabyDestBLOB =
    8855           0 :         GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
    8856           0 :     if (!pabyDestBLOB)
    8857             :     {
    8858           0 :         sqlite3_result_null(pContext);
    8859           0 :         return;
    8860             :     }
    8861           0 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    8862             :                         VSIFree);
    8863             : }
    8864             : 
    8865             : /************************************************************************/
    8866             : /*                    OGRGeoPackageSridFromAuthCRS()                    */
    8867             : /************************************************************************/
    8868             : 
    8869           4 : static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
    8870             :                                          int /*argc*/, sqlite3_value **argv)
    8871             : {
    8872           7 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8873           3 :         sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
    8874             :     {
    8875           2 :         sqlite3_result_int(pContext, -1);
    8876           2 :         return;
    8877             :     }
    8878             : 
    8879             :     GDALGeoPackageDataset *poDS =
    8880           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8881             : 
    8882           2 :     char *pszSQL = sqlite3_mprintf(
    8883             :         "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
    8884             :         "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
    8885           2 :         sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
    8886           2 :     OGRErr err = OGRERR_NONE;
    8887           2 :     int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
    8888           2 :     sqlite3_free(pszSQL);
    8889           2 :     if (err != OGRERR_NONE)
    8890           1 :         nSRSId = -1;
    8891           2 :     sqlite3_result_int(pContext, nSRSId);
    8892             : }
    8893             : 
    8894             : /************************************************************************/
    8895             : /*                    OGRGeoPackageImportFromEPSG()                     */
    8896             : /************************************************************************/
    8897             : 
    8898           4 : static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
    8899             :                                         sqlite3_value **argv)
    8900             : {
    8901           4 :     if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
    8902             :     {
    8903           1 :         sqlite3_result_int(pContext, -1);
    8904           2 :         return;
    8905             :     }
    8906             : 
    8907             :     GDALGeoPackageDataset *poDS =
    8908           3 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8909           3 :     OGRSpatialReference oSRS;
    8910           3 :     if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
    8911             :     {
    8912           1 :         sqlite3_result_int(pContext, -1);
    8913           1 :         return;
    8914             :     }
    8915             : 
    8916           2 :     sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
    8917             : }
    8918             : 
    8919             : /************************************************************************/
    8920             : /*               OGRGeoPackageRegisterGeometryExtension()               */
    8921             : /************************************************************************/
    8922             : 
    8923           1 : static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
    8924             :                                                    int /*argc*/,
    8925             :                                                    sqlite3_value **argv)
    8926             : {
    8927           1 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8928           2 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
    8929           1 :         sqlite3_value_type(argv[2]) != SQLITE_TEXT)
    8930             :     {
    8931           0 :         sqlite3_result_int(pContext, 0);
    8932           0 :         return;
    8933             :     }
    8934             : 
    8935             :     const char *pszTableName =
    8936           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8937             :     const char *pszGeomName =
    8938           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8939             :     const char *pszGeomType =
    8940           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
    8941             : 
    8942             :     GDALGeoPackageDataset *poDS =
    8943           1 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8944             : 
    8945           1 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    8946           1 :         poDS->GetLayerByName(pszTableName));
    8947           1 :     if (poLyr == nullptr)
    8948             :     {
    8949           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    8950           0 :         sqlite3_result_int(pContext, 0);
    8951           0 :         return;
    8952             :     }
    8953           1 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    8954             :     {
    8955           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    8956           0 :         sqlite3_result_int(pContext, 0);
    8957           0 :         return;
    8958             :     }
    8959           1 :     const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
    8960           1 :     if (eGeomType == wkbUnknown)
    8961             :     {
    8962           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
    8963           0 :         sqlite3_result_int(pContext, 0);
    8964           0 :         return;
    8965             :     }
    8966             : 
    8967           1 :     sqlite3_result_int(
    8968             :         pContext,
    8969           1 :         static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
    8970             : }
    8971             : 
    8972             : /************************************************************************/
    8973             : /*                  OGRGeoPackageCreateSpatialIndex()                   */
    8974             : /************************************************************************/
    8975             : 
    8976          14 : static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
    8977             :                                             int /*argc*/, sqlite3_value **argv)
    8978             : {
    8979          27 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8980          13 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    8981             :     {
    8982           2 :         sqlite3_result_int(pContext, 0);
    8983           2 :         return;
    8984             :     }
    8985             : 
    8986             :     const char *pszTableName =
    8987          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8988             :     const char *pszGeomName =
    8989          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8990             :     GDALGeoPackageDataset *poDS =
    8991          12 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8992             : 
    8993          12 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    8994          12 :         poDS->GetLayerByName(pszTableName));
    8995          12 :     if (poLyr == nullptr)
    8996             :     {
    8997           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    8998           1 :         sqlite3_result_int(pContext, 0);
    8999           1 :         return;
    9000             :     }
    9001          11 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9002             :     {
    9003           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9004           1 :         sqlite3_result_int(pContext, 0);
    9005           1 :         return;
    9006             :     }
    9007             : 
    9008          10 :     sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
    9009             : }
    9010             : 
    9011             : /************************************************************************/
    9012             : /*                  OGRGeoPackageDisableSpatialIndex()                  */
    9013             : /************************************************************************/
    9014             : 
    9015          12 : static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
    9016             :                                              int /*argc*/, sqlite3_value **argv)
    9017             : {
    9018          23 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9019          11 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9020             :     {
    9021           2 :         sqlite3_result_int(pContext, 0);
    9022           2 :         return;
    9023             :     }
    9024             : 
    9025             :     const char *pszTableName =
    9026          10 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9027             :     const char *pszGeomName =
    9028          10 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9029             :     GDALGeoPackageDataset *poDS =
    9030          10 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9031             : 
    9032          10 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9033          10 :         poDS->GetLayerByName(pszTableName));
    9034          10 :     if (poLyr == nullptr)
    9035             :     {
    9036           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9037           1 :         sqlite3_result_int(pContext, 0);
    9038           1 :         return;
    9039             :     }
    9040           9 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9041             :     {
    9042           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9043           1 :         sqlite3_result_int(pContext, 0);
    9044           1 :         return;
    9045             :     }
    9046             : 
    9047           8 :     sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
    9048             : }
    9049             : 
    9050             : /************************************************************************/
    9051             : /*                    OGRGeoPackageHasSpatialIndex()                    */
    9052             : /************************************************************************/
    9053             : 
    9054          29 : static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
    9055             :                                          int /*argc*/, sqlite3_value **argv)
    9056             : {
    9057          57 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9058          28 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9059             :     {
    9060           2 :         sqlite3_result_int(pContext, 0);
    9061           2 :         return;
    9062             :     }
    9063             : 
    9064             :     const char *pszTableName =
    9065          27 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9066             :     const char *pszGeomName =
    9067          27 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9068             :     GDALGeoPackageDataset *poDS =
    9069          27 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9070             : 
    9071          27 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9072          27 :         poDS->GetLayerByName(pszTableName));
    9073          27 :     if (poLyr == nullptr)
    9074             :     {
    9075           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9076           1 :         sqlite3_result_int(pContext, 0);
    9077           1 :         return;
    9078             :     }
    9079          26 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9080             :     {
    9081           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9082           1 :         sqlite3_result_int(pContext, 0);
    9083           1 :         return;
    9084             :     }
    9085             : 
    9086          25 :     poLyr->RunDeferredCreationIfNecessary();
    9087          25 :     poLyr->CreateSpatialIndexIfNecessary();
    9088             : 
    9089          25 :     sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
    9090             : }
    9091             : 
    9092             : /************************************************************************/
    9093             : /*                       GPKG_hstore_get_value()                        */
    9094             : /************************************************************************/
    9095             : 
    9096           4 : static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
    9097             :                                   sqlite3_value **argv)
    9098             : {
    9099           7 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9100           3 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9101             :     {
    9102           2 :         sqlite3_result_null(pContext);
    9103           2 :         return;
    9104             :     }
    9105             : 
    9106             :     const char *pszHStore =
    9107           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9108             :     const char *pszSearchedKey =
    9109           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9110           2 :     char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
    9111           2 :     if (pszValue != nullptr)
    9112           1 :         sqlite3_result_text(pContext, pszValue, -1, CPLFree);
    9113             :     else
    9114           1 :         sqlite3_result_null(pContext);
    9115             : }
    9116             : 
    9117             : /************************************************************************/
    9118             : /*                    GPKG_GDAL_GetMemFileFromBlob()                    */
    9119             : /************************************************************************/
    9120             : 
    9121         105 : static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
    9122             : {
    9123         105 :     int nBytes = sqlite3_value_bytes(argv[0]);
    9124             :     const GByte *pabyBLOB =
    9125         105 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    9126             :     CPLString osMemFileName(
    9127         105 :         VSIMemGenerateHiddenFilename("GPKG_GDAL_GetMemFileFromBlob"));
    9128         105 :     VSILFILE *fp = VSIFileFromMemBuffer(
    9129             :         osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
    9130         105 :     VSIFCloseL(fp);
    9131         105 :     return osMemFileName;
    9132             : }
    9133             : 
    9134             : /************************************************************************/
    9135             : /*                       GPKG_GDAL_GetMimeType()                        */
    9136             : /************************************************************************/
    9137             : 
    9138          35 : static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
    9139             :                                   sqlite3_value **argv)
    9140             : {
    9141          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9142             :     {
    9143           0 :         sqlite3_result_null(pContext);
    9144           0 :         return;
    9145             :     }
    9146             : 
    9147          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9148             :     GDALDriver *poDriver =
    9149          35 :         GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
    9150          35 :     if (poDriver != nullptr)
    9151             :     {
    9152          35 :         const char *pszRes = nullptr;
    9153          35 :         if (EQUAL(poDriver->GetDescription(), "PNG"))
    9154          23 :             pszRes = "image/png";
    9155          12 :         else if (EQUAL(poDriver->GetDescription(), "JPEG"))
    9156           6 :             pszRes = "image/jpeg";
    9157           6 :         else if (EQUAL(poDriver->GetDescription(), "WEBP"))
    9158           6 :             pszRes = "image/x-webp";
    9159           0 :         else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
    9160           0 :             pszRes = "image/tiff";
    9161             :         else
    9162           0 :             pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
    9163          35 :         sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
    9164             :     }
    9165             :     else
    9166           0 :         sqlite3_result_null(pContext);
    9167          35 :     VSIUnlink(osMemFileName);
    9168             : }
    9169             : 
    9170             : /************************************************************************/
    9171             : /*                       GPKG_GDAL_GetBandCount()                       */
    9172             : /************************************************************************/
    9173             : 
    9174          35 : static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
    9175             :                                    sqlite3_value **argv)
    9176             : {
    9177          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9178             :     {
    9179           0 :         sqlite3_result_null(pContext);
    9180           0 :         return;
    9181             :     }
    9182             : 
    9183          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9184             :     auto poDS = std::unique_ptr<GDALDataset>(
    9185             :         GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    9186          70 :                           nullptr, nullptr, nullptr));
    9187          35 :     if (poDS != nullptr)
    9188             :     {
    9189          35 :         sqlite3_result_int(pContext, poDS->GetRasterCount());
    9190             :     }
    9191             :     else
    9192           0 :         sqlite3_result_null(pContext);
    9193          35 :     VSIUnlink(osMemFileName);
    9194             : }
    9195             : 
    9196             : /************************************************************************/
    9197             : /*                      GPKG_GDAL_HasColorTable()                       */
    9198             : /************************************************************************/
    9199             : 
    9200          35 : static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
    9201             :                                     sqlite3_value **argv)
    9202             : {
    9203          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9204             :     {
    9205           0 :         sqlite3_result_null(pContext);
    9206           0 :         return;
    9207             :     }
    9208             : 
    9209          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9210             :     auto poDS = std::unique_ptr<GDALDataset>(
    9211             :         GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    9212          70 :                           nullptr, nullptr, nullptr));
    9213          35 :     if (poDS != nullptr)
    9214             :     {
    9215          35 :         sqlite3_result_int(
    9216          46 :             pContext, poDS->GetRasterCount() == 1 &&
    9217          11 :                           poDS->GetRasterBand(1)->GetColorTable() != nullptr);
    9218             :     }
    9219             :     else
    9220           0 :         sqlite3_result_null(pContext);
    9221          35 :     VSIUnlink(osMemFileName);
    9222             : }
    9223             : 
    9224             : /************************************************************************/
    9225             : /*                       GetRasterLayerDataset()                        */
    9226             : /************************************************************************/
    9227             : 
    9228             : GDALDataset *
    9229          12 : GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
    9230             : {
    9231          12 :     const auto oIter = m_oCachedRasterDS.find(pszLayerName);
    9232          12 :     if (oIter != m_oCachedRasterDS.end())
    9233          10 :         return oIter->second.get();
    9234             : 
    9235             :     auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
    9236           4 :         (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
    9237           4 :         GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
    9238           2 :     if (!poDS)
    9239             :     {
    9240           0 :         return nullptr;
    9241             :     }
    9242           2 :     m_oCachedRasterDS[pszLayerName] = std::move(poDS);
    9243           2 :     return m_oCachedRasterDS[pszLayerName].get();
    9244             : }
    9245             : 
    9246             : /************************************************************************/
    9247             : /*                  GPKG_gdal_get_layer_pixel_value()                   */
    9248             : /************************************************************************/
    9249             : 
    9250             : // NOTE: keep in sync implementations in ogrsqlitesqlfunctionscommon.cpp
    9251             : // and ogrgeopackagedatasource.cpp
    9252          13 : static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext, int argc,
    9253             :                                             sqlite3_value **argv)
    9254             : {
    9255          13 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
    9256             :     {
    9257           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    9258             :                  "Invalid arguments to gdal_get_layer_pixel_value()");
    9259           1 :         sqlite3_result_null(pContext);
    9260           1 :         return;
    9261             :     }
    9262             : 
    9263             :     const char *pszLayerName =
    9264          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9265             : 
    9266             :     GDALGeoPackageDataset *poGlobalDS =
    9267          12 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9268          12 :     auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
    9269          12 :     if (!poDS)
    9270             :     {
    9271           0 :         sqlite3_result_null(pContext);
    9272           0 :         return;
    9273             :     }
    9274             : 
    9275          12 :     OGRSQLite_gdal_get_pixel_value_common("gdal_get_layer_pixel_value",
    9276             :                                           pContext, argc, argv, poDS);
    9277             : }
    9278             : 
    9279             : /************************************************************************/
    9280             : /*                       GPKG_ogr_layer_Extent()                        */
    9281             : /************************************************************************/
    9282             : 
    9283           3 : static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
    9284             :                                   sqlite3_value **argv)
    9285             : {
    9286           3 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
    9287             :     {
    9288           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
    9289             :                  "ogr_layer_Extent");
    9290           1 :         sqlite3_result_null(pContext);
    9291           2 :         return;
    9292             :     }
    9293             : 
    9294             :     const char *pszLayerName =
    9295           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9296             :     GDALGeoPackageDataset *poDS =
    9297           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9298           2 :     OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
    9299           2 :     if (!poLayer)
    9300             :     {
    9301           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
    9302             :                  "ogr_layer_Extent");
    9303           1 :         sqlite3_result_null(pContext);
    9304           1 :         return;
    9305             :     }
    9306             : 
    9307           1 :     if (poLayer->GetGeomType() == wkbNone)
    9308             :     {
    9309           0 :         sqlite3_result_null(pContext);
    9310           0 :         return;
    9311             :     }
    9312             : 
    9313           1 :     OGREnvelope sExtent;
    9314           1 :     if (poLayer->GetExtent(&sExtent, true) != OGRERR_NONE)
    9315             :     {
    9316           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
    9317             :                  "ogr_layer_Extent");
    9318           0 :         sqlite3_result_null(pContext);
    9319           0 :         return;
    9320             :     }
    9321             : 
    9322           1 :     OGRPolygon oPoly;
    9323           1 :     auto poRing = std::make_unique<OGRLinearRing>();
    9324           1 :     poRing->addPoint(sExtent.MinX, sExtent.MinY);
    9325           1 :     poRing->addPoint(sExtent.MaxX, sExtent.MinY);
    9326           1 :     poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
    9327           1 :     poRing->addPoint(sExtent.MinX, sExtent.MaxY);
    9328           1 :     poRing->addPoint(sExtent.MinX, sExtent.MinY);
    9329           1 :     oPoly.addRing(std::move(poRing));
    9330             : 
    9331           1 :     const auto poSRS = poLayer->GetSpatialRef();
    9332           1 :     const int nSRID = poDS->GetSrsId(poSRS);
    9333           1 :     size_t nBLOBDestLen = 0;
    9334             :     GByte *pabyDestBLOB =
    9335           1 :         GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
    9336           1 :     if (!pabyDestBLOB)
    9337             :     {
    9338           0 :         sqlite3_result_null(pContext);
    9339           0 :         return;
    9340             :     }
    9341           1 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    9342             :                         VSIFree);
    9343             : }
    9344             : 
    9345             : /************************************************************************/
    9346             : /*                   GPKG_ST_Hilbert_X_Y_TableName()                    */
    9347             : /************************************************************************/
    9348             : 
    9349           8 : static void GPKG_ST_Hilbert_X_Y_TableName(sqlite3_context *pContext,
    9350             :                                           [[maybe_unused]] int argc,
    9351             :                                           sqlite3_value **argv)
    9352             : {
    9353           8 :     CPLAssert(argc == 3);
    9354           8 :     const double dfX = sqlite3_value_double(argv[0]);
    9355           8 :     const double dfY = sqlite3_value_double(argv[1]);
    9356             : 
    9357           8 :     if (sqlite3_value_type(argv[2]) != SQLITE_TEXT)
    9358             :     {
    9359           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    9360             :                  "%s: Invalid argument type for 3rd argument. Text expected",
    9361             :                  "ST_Hilbert()");
    9362           1 :         sqlite3_result_null(pContext);
    9363           6 :         return;
    9364             :     }
    9365             : 
    9366             :     const char *pszLayerName =
    9367           7 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
    9368             :     GDALGeoPackageDataset *poDS =
    9369           7 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9370           7 :     OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
    9371           7 :     if (!poLayer)
    9372             :     {
    9373           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer '%s'",
    9374             :                  "ST_Hilbert()", pszLayerName);
    9375           1 :         sqlite3_result_null(pContext);
    9376           1 :         return;
    9377             :     }
    9378             : 
    9379           6 :     OGREnvelope sExtent;
    9380           6 :     if (poLayer->GetExtent(&sExtent, true) != OGRERR_NONE)
    9381             :     {
    9382           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
    9383             :                  "ST_Hilbert()");
    9384           0 :         sqlite3_result_null(pContext);
    9385           0 :         return;
    9386             :     }
    9387           6 :     if (!(dfX >= sExtent.MinX && dfY >= sExtent.MinY && dfX <= sExtent.MaxX &&
    9388           3 :           dfY <= sExtent.MaxY))
    9389             :     {
    9390           4 :         CPLError(CE_Warning, CPLE_AppDefined,
    9391             :                  "ST_Hilbert(): (%g, %g) is not within passed bounding box",
    9392             :                  dfX, dfY);
    9393           4 :         sqlite3_result_null(pContext);
    9394           4 :         return;
    9395             :     }
    9396           2 :     sqlite3_result_int64(pContext, GDALHilbertCode(&sExtent, dfX, dfY));
    9397             : }
    9398             : 
    9399             : /************************************************************************/
    9400             : /*                     GPKG_ST_Hilbert_Geom_BBOX()                      */
    9401             : /************************************************************************/
    9402             : 
    9403           6 : static void GPKG_ST_Hilbert_Geom_BBOX(sqlite3_context *pContext, int argc,
    9404             :                                       sqlite3_value **argv)
    9405             : {
    9406           6 :     CPLAssert(argc == 5);
    9407             :     GPkgHeader sHeader;
    9408           6 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    9409             :     {
    9410           1 :         sqlite3_result_null(pContext);
    9411           5 :         return;
    9412             :     }
    9413           5 :     const double dfX = (sHeader.MinX + sHeader.MaxX) / 2;
    9414           5 :     const double dfY = (sHeader.MinY + sHeader.MaxY) / 2;
    9415             : 
    9416           5 :     OGREnvelope sExtent;
    9417           5 :     sExtent.MinX = sqlite3_value_double(argv[1]);
    9418           5 :     sExtent.MinY = sqlite3_value_double(argv[2]);
    9419           5 :     sExtent.MaxX = sqlite3_value_double(argv[3]);
    9420           5 :     sExtent.MaxY = sqlite3_value_double(argv[4]);
    9421           5 :     if (!(dfX >= sExtent.MinX && dfY >= sExtent.MinY && dfX <= sExtent.MaxX &&
    9422           2 :           dfY <= sExtent.MaxY))
    9423             :     {
    9424           4 :         CPLError(CE_Warning, CPLE_AppDefined,
    9425             :                  "ST_Hilbert(): (%g, %g) is not within passed bounding box",
    9426             :                  dfX, dfY);
    9427           4 :         sqlite3_result_null(pContext);
    9428           4 :         return;
    9429             :     }
    9430           1 :     sqlite3_result_int64(pContext, GDALHilbertCode(&sExtent, dfX, dfY));
    9431             : }
    9432             : 
    9433             : /************************************************************************/
    9434             : /*                   GPKG_ST_Hilbert_Geom_TableName()                   */
    9435             : /************************************************************************/
    9436             : 
    9437           4 : static void GPKG_ST_Hilbert_Geom_TableName(sqlite3_context *pContext, int argc,
    9438             :                                            sqlite3_value **argv)
    9439             : {
    9440           4 :     CPLAssert(argc == 2);
    9441             :     GPkgHeader sHeader;
    9442           4 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    9443             :     {
    9444           1 :         sqlite3_result_null(pContext);
    9445           3 :         return;
    9446             :     }
    9447           3 :     const double dfX = (sHeader.MinX + sHeader.MaxX) / 2;
    9448           3 :     const double dfY = (sHeader.MinY + sHeader.MaxY) / 2;
    9449             : 
    9450           3 :     if (sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9451             :     {
    9452           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    9453             :                  "%s: Invalid argument type for 2nd argument. Text expected",
    9454             :                  "ST_Hilbert()");
    9455           1 :         sqlite3_result_null(pContext);
    9456           1 :         return;
    9457             :     }
    9458             : 
    9459             :     const char *pszLayerName =
    9460           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9461             :     GDALGeoPackageDataset *poDS =
    9462           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9463           2 :     OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
    9464           2 :     if (!poLayer)
    9465             :     {
    9466           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer '%s'",
    9467             :                  "ST_Hilbert()", pszLayerName);
    9468           1 :         sqlite3_result_null(pContext);
    9469           1 :         return;
    9470             :     }
    9471             : 
    9472           1 :     OGREnvelope sExtent;
    9473           1 :     if (poLayer->GetExtent(&sExtent, true) != OGRERR_NONE)
    9474             :     {
    9475           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
    9476             :                  "ST_Hilbert()");
    9477           0 :         sqlite3_result_null(pContext);
    9478           0 :         return;
    9479             :     }
    9480           1 :     if (!(dfX >= sExtent.MinX && dfY >= sExtent.MinY && dfX <= sExtent.MaxX &&
    9481           1 :           dfY <= sExtent.MaxY))
    9482             :     {
    9483           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9484             :                  "ST_Hilbert(): (%g, %g) is not within passed bounding box",
    9485             :                  dfX, dfY);
    9486           0 :         sqlite3_result_null(pContext);
    9487           0 :         return;
    9488             :     }
    9489           1 :     sqlite3_result_int64(pContext, GDALHilbertCode(&sExtent, dfX, dfY));
    9490             : }
    9491             : 
    9492             : /************************************************************************/
    9493             : /*                        InstallSQLFunctions()                         */
    9494             : /************************************************************************/
    9495             : 
    9496             : #ifndef SQLITE_DETERMINISTIC
    9497             : #define SQLITE_DETERMINISTIC 0
    9498             : #endif
    9499             : 
    9500             : #ifndef SQLITE_INNOCUOUS
    9501             : #define SQLITE_INNOCUOUS 0
    9502             : #endif
    9503             : 
    9504             : #ifndef UTF8_INNOCUOUS
    9505             : #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
    9506             : #endif
    9507             : 
    9508        2307 : void GDALGeoPackageDataset::InstallSQLFunctions()
    9509             : {
    9510        2307 :     InitSpatialite();
    9511             : 
    9512             :     // Enable SpatiaLite 4.3 GPKG mode, i.e. that SpatiaLite functions
    9513             :     // that take geometries will accept and return GPKG encoded geometries without
    9514             :     // explicit conversion.
    9515             :     // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
    9516             :     // error.
    9517        2307 :     sqlite3_exec(hDB, "SELECT EnableGpkgMode()", nullptr, nullptr, nullptr);
    9518             : 
    9519             :     /* Used by RTree Spatial Index Extension */
    9520        2307 :     sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
    9521             :                             OGRGeoPackageSTMinX, nullptr, nullptr);
    9522        2307 :     sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
    9523             :                             OGRGeoPackageSTMinY, nullptr, nullptr);
    9524        2307 :     sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
    9525             :                             OGRGeoPackageSTMaxX, nullptr, nullptr);
    9526        2307 :     sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
    9527             :                             OGRGeoPackageSTMaxY, nullptr, nullptr);
    9528        2307 :     sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
    9529             :                             OGRGeoPackageSTIsEmpty, nullptr, nullptr);
    9530             : 
    9531             :     /* Used by Geometry Type Triggers Extension */
    9532        2307 :     sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
    9533             :                             OGRGeoPackageSTGeometryType, nullptr, nullptr);
    9534        2307 :     sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
    9535             :                             nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
    9536             :                             nullptr);
    9537             : 
    9538             :     /* Used by Geometry SRS ID Triggers Extension */
    9539        2307 :     sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
    9540             :                             OGRGeoPackageSTSRID, nullptr, nullptr);
    9541             : 
    9542             :     /* Spatialite-like functions */
    9543        2307 :     sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
    9544             :                             OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
    9545        2307 :     sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
    9546             :                             OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
    9547        2307 :     sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
    9548             :                             OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
    9549             : 
    9550             :     // HSTORE functions
    9551        2307 :     sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
    9552             :                             GPKG_hstore_get_value, nullptr, nullptr);
    9553             : 
    9554             :     // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
    9555        2307 :     sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
    9556             :                             OGRGeoPackageTransform, nullptr, nullptr);
    9557        2307 :     sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
    9558             :                             OGRGeoPackageTransform, nullptr, nullptr);
    9559        2307 :     sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
    9560             :                             OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
    9561             : 
    9562        2307 :     sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
    9563             :                             OGRGeoPackageSTEnvelopesIntersectsTwoParams,
    9564             :                             nullptr, nullptr);
    9565        2307 :     sqlite3_create_function(
    9566             :         hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
    9567             :         OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
    9568             : 
    9569        2307 :     sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
    9570             :                             OGRGeoPackageSTEnvelopesIntersects, nullptr,
    9571             :                             nullptr);
    9572        2307 :     sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
    9573             :                             nullptr, OGRGeoPackageSTEnvelopesIntersects,
    9574             :                             nullptr, nullptr);
    9575             : 
    9576             :     // Implementation that directly hacks the GeoPackage geometry blob header
    9577        2307 :     sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
    9578             :                             OGRGeoPackageSetSRID, nullptr, nullptr);
    9579             : 
    9580             :     // GDAL specific function
    9581        2307 :     sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
    9582             :                             OGRGeoPackageImportFromEPSG, nullptr, nullptr);
    9583             : 
    9584             :     // May be used by ogrmerge.py
    9585        2307 :     sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
    9586             :                             this, OGRGeoPackageRegisterGeometryExtension,
    9587             :                             nullptr, nullptr);
    9588             : 
    9589        2307 :     if (OGRGeometryFactory::haveGEOS())
    9590             :     {
    9591        2307 :         sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
    9592             :                                 OGRGeoPackageSTMakeValid, nullptr, nullptr);
    9593             :     }
    9594             : 
    9595        2307 :     sqlite3_create_function(hDB, "ST_Length", 1, UTF8_INNOCUOUS, nullptr,
    9596             :                             OGRGeoPackageLengthOrGeodesicLength, nullptr,
    9597             :                             nullptr);
    9598        2307 :     sqlite3_create_function(hDB, "ST_Length", 2, UTF8_INNOCUOUS, this,
    9599             :                             OGRGeoPackageLengthOrGeodesicLength, nullptr,
    9600             :                             nullptr);
    9601             : 
    9602        2307 :     sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
    9603             :                             OGRGeoPackageSTArea, nullptr, nullptr);
    9604        2307 :     sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
    9605             :                             OGRGeoPackageGeodesicArea, nullptr, nullptr);
    9606             : 
    9607             :     // Debug functions
    9608        2307 :     if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
    9609             :     {
    9610         422 :         sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
    9611             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9612             :                                 GPKG_GDAL_GetMimeType, nullptr, nullptr);
    9613         422 :         sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
    9614             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9615             :                                 GPKG_GDAL_GetBandCount, nullptr, nullptr);
    9616         422 :         sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
    9617             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9618             :                                 GPKG_GDAL_HasColorTable, nullptr, nullptr);
    9619             :     }
    9620             : 
    9621        2307 :     sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
    9622             :                             this, GPKG_gdal_get_layer_pixel_value, nullptr,
    9623             :                             nullptr);
    9624        2307 :     sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 6, SQLITE_UTF8,
    9625             :                             this, GPKG_gdal_get_layer_pixel_value, nullptr,
    9626             :                             nullptr);
    9627             : 
    9628             :     // Function from VirtualOGR
    9629        2307 :     sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
    9630             :                             GPKG_ogr_layer_Extent, nullptr, nullptr);
    9631             : 
    9632        2307 :     m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
    9633             : 
    9634             :     // ST_Hilbert() inspired from https://duckdb.org/docs/stable/core_extensions/spatial/functions#st_hilbert
    9635             :     // Override the generic version of OGRSQLiteRegisterSQLFunctionsCommon()
    9636             : 
    9637             :     // X,Y,table_name
    9638        2307 :     sqlite3_create_function(hDB, "ST_Hilbert", 2 + 1, UTF8_INNOCUOUS, this,
    9639             :                             GPKG_ST_Hilbert_X_Y_TableName, nullptr, nullptr);
    9640             : 
    9641             :     // geometry,minX,minY,maxX,maxY
    9642        2307 :     sqlite3_create_function(hDB, "ST_Hilbert", 1 + 4, UTF8_INNOCUOUS, nullptr,
    9643             :                             GPKG_ST_Hilbert_Geom_BBOX, nullptr, nullptr);
    9644             : 
    9645             :     // geometry,table_name
    9646        2307 :     sqlite3_create_function(hDB, "ST_Hilbert", 1 + 1, UTF8_INNOCUOUS, this,
    9647             :                             GPKG_ST_Hilbert_Geom_TableName, nullptr, nullptr);
    9648        2307 : }
    9649             : 
    9650             : /************************************************************************/
    9651             : /*                           OpenOrCreateDB()                           */
    9652             : /************************************************************************/
    9653             : 
    9654        2312 : bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
    9655             : {
    9656        2312 :     const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
    9657             :         flags, /*bRegisterOGR2SQLiteExtensions=*/false,
    9658             :         /*bLoadExtensions=*/true);
    9659        2312 :     if (!bSuccess)
    9660          10 :         return false;
    9661             : 
    9662             :     // Turning on recursive_triggers is needed so that DELETE triggers fire
    9663             :     // in a INSERT OR REPLACE statement. In particular this is needed to
    9664             :     // make sure gpkg_ogr_contents.feature_count is properly updated.
    9665        2302 :     SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
    9666             : 
    9667        2302 :     InstallSQLFunctions();
    9668             : 
    9669             :     const char *pszSqlitePragma =
    9670        2302 :         CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
    9671        2302 :     OGRErr eErr = OGRERR_NONE;
    9672           6 :     if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
    9673             :         // Older sqlite versions don't have this pragma
    9674        4610 :         SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
    9675        2302 :         eErr == OGRERR_NONE)
    9676             :     {
    9677        2302 :         bool bNeedsTrustedSchema = false;
    9678             : 
    9679             :         // Current SQLite versions require PRAGMA trusted_schema = 1 to be
    9680             :         // able to use the RTree from triggers, which is only needed when
    9681             :         // modifying the RTree.
    9682        5656 :         if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
    9683        3552 :              (flags & SQLITE_OPEN_CREATE) != 0) &&
    9684        1250 :             OGRSQLiteRTreeRequiresTrustedSchemaOn())
    9685             :         {
    9686        1250 :             bNeedsTrustedSchema = true;
    9687             :         }
    9688             : 
    9689             : #ifdef HAVE_SPATIALITE
    9690             :         // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
    9691        1052 :         if (!bNeedsTrustedSchema && HasExtensionsTable() &&
    9692         961 :             SQLGetInteger(
    9693             :                 hDB,
    9694             :                 "SELECT 1 FROM gpkg_extensions WHERE "
    9695             :                 "extension_name ='gdal_spatialite_computed_geom_column'",
    9696           1 :                 nullptr) == 1 &&
    9697        3354 :             SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
    9698             :         {
    9699           1 :             bNeedsTrustedSchema = true;
    9700             :         }
    9701             : #endif
    9702             : 
    9703        2302 :         if (bNeedsTrustedSchema)
    9704             :         {
    9705        1251 :             CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
    9706        1251 :             SQLCommand(hDB, "PRAGMA trusted_schema = 1");
    9707             :         }
    9708             :     }
    9709             : 
    9710             :     const char *pszPreludeStatements =
    9711        2302 :         CSLFetchNameValue(papszOpenOptions, "PRELUDE_STATEMENTS");
    9712        2302 :     if (pszPreludeStatements)
    9713             :     {
    9714           2 :         if (SQLCommand(hDB, pszPreludeStatements) != OGRERR_NONE)
    9715           0 :             return false;
    9716             :     }
    9717             : 
    9718        2302 :     return true;
    9719             : }
    9720             : 
    9721             : /************************************************************************/
    9722             : /*                 GetLayerWithGetSpatialWhereByName()                  */
    9723             : /************************************************************************/
    9724             : 
    9725             : std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
    9726          90 : GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
    9727             : {
    9728             :     OGRGeoPackageLayer *poRet =
    9729          90 :         cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
    9730          90 :     return std::pair(poRet, poRet);
    9731             : }
    9732             : 
    9733             : /************************************************************************/
    9734             : /*                         CommitTransaction()                          */
    9735             : /************************************************************************/
    9736             : 
    9737         340 : OGRErr GDALGeoPackageDataset::CommitTransaction()
    9738             : 
    9739             : {
    9740         340 :     if (m_nSoftTransactionLevel == 1)
    9741             :     {
    9742         334 :         FlushMetadata();
    9743         720 :         for (auto &poLayer : m_apoLayers)
    9744             :         {
    9745         386 :             poLayer->DoJobAtTransactionCommit();
    9746             :         }
    9747             :     }
    9748             : 
    9749         340 :     return OGRSQLiteBaseDataSource::CommitTransaction();
    9750             : }
    9751             : 
    9752             : /************************************************************************/
    9753             : /*                        RollbackTransaction()                         */
    9754             : /************************************************************************/
    9755             : 
    9756          35 : OGRErr GDALGeoPackageDataset::RollbackTransaction()
    9757             : 
    9758             : {
    9759             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9760          70 :     std::vector<bool> abAddTriggers;
    9761          35 :     std::vector<bool> abTriggersDeletedInTransaction;
    9762             : #endif
    9763          35 :     if (m_nSoftTransactionLevel == 1)
    9764             :     {
    9765          34 :         FlushMetadata();
    9766          70 :         for (auto &poLayer : m_apoLayers)
    9767             :         {
    9768             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9769          36 :             abAddTriggers.push_back(poLayer->GetAddOGRFeatureCountTriggers());
    9770          36 :             abTriggersDeletedInTransaction.push_back(
    9771          36 :                 poLayer->GetOGRFeatureCountTriggersDeletedInTransaction());
    9772          36 :             poLayer->SetAddOGRFeatureCountTriggers(false);
    9773             : #endif
    9774          36 :             poLayer->DoJobAtTransactionRollback();
    9775             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9776          36 :             poLayer->DisableFeatureCount();
    9777             : #endif
    9778             :         }
    9779             :     }
    9780             : 
    9781          35 :     const OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
    9782             : 
    9783             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9784          35 :     if (!abAddTriggers.empty())
    9785             :     {
    9786          68 :         for (size_t i = 0; i < m_apoLayers.size(); ++i)
    9787             :         {
    9788          36 :             auto &poLayer = m_apoLayers[i];
    9789          36 :             if (abTriggersDeletedInTransaction[i])
    9790             :             {
    9791           7 :                 poLayer->SetOGRFeatureCountTriggersEnabled(true);
    9792             :             }
    9793             :             else
    9794             :             {
    9795          29 :                 poLayer->SetAddOGRFeatureCountTriggers(abAddTriggers[i]);
    9796             :             }
    9797             :         }
    9798             :     }
    9799             : #endif
    9800          70 :     return eErr;
    9801             : }
    9802             : 
    9803             : /************************************************************************/
    9804             : /*                       GetGeometryTypeString()                        */
    9805             : /************************************************************************/
    9806             : 
    9807             : const char *
    9808        1653 : GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
    9809             : {
    9810        1653 :     const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
    9811        1665 :     if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
    9812          12 :         CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
    9813             :     {
    9814           0 :         pszGPKGGeomType = "GEOMCOLLECTION";
    9815             :     }
    9816        1653 :     return pszGPKGGeomType;
    9817             : }
    9818             : 
    9819             : /************************************************************************/
    9820             : /*                        GetFieldDomainNames()                         */
    9821             : /************************************************************************/
    9822             : 
    9823             : std::vector<std::string>
    9824          12 : GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
    9825             : {
    9826          12 :     if (!HasDataColumnConstraintsTable())
    9827           3 :         return std::vector<std::string>();
    9828             : 
    9829          18 :     std::vector<std::string> oDomainNamesList;
    9830             : 
    9831           9 :     std::unique_ptr<SQLResult> oResultTable;
    9832             :     {
    9833             :         std::string osSQL =
    9834             :             "SELECT DISTINCT constraint_name "
    9835             :             "FROM gpkg_data_column_constraints "
    9836             :             "WHERE constraint_name NOT LIKE '_%_domain_description' "
    9837             :             "ORDER BY constraint_name "
    9838           9 :             "LIMIT 10000"  // to avoid denial of service
    9839             :             ;
    9840           9 :         oResultTable = SQLQuery(hDB, osSQL.c_str());
    9841           9 :         if (!oResultTable)
    9842           0 :             return oDomainNamesList;
    9843             :     }
    9844             : 
    9845           9 :     if (oResultTable->RowCount() == 10000)
    9846             :     {
    9847           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9848             :                  "Number of rows returned for field domain names has been "
    9849             :                  "truncated.");
    9850             :     }
    9851           9 :     else if (oResultTable->RowCount() > 0)
    9852             :     {
    9853           8 :         oDomainNamesList.reserve(oResultTable->RowCount());
    9854         105 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    9855             :         {
    9856          97 :             const char *pszConstraintName = oResultTable->GetValue(0, i);
    9857          97 :             if (!pszConstraintName)
    9858           0 :                 continue;
    9859             : 
    9860          97 :             oDomainNamesList.emplace_back(pszConstraintName);
    9861             :         }
    9862             :     }
    9863             : 
    9864           9 :     return oDomainNamesList;
    9865             : }
    9866             : 
    9867             : /************************************************************************/
    9868             : /*                           GetFieldDomain()                           */
    9869             : /************************************************************************/
    9870             : 
    9871             : const OGRFieldDomain *
    9872         138 : GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
    9873             : {
    9874         138 :     const auto baseRet = GDALDataset::GetFieldDomain(name);
    9875         138 :     if (baseRet)
    9876          43 :         return baseRet;
    9877             : 
    9878          95 :     if (!HasDataColumnConstraintsTable())
    9879           4 :         return nullptr;
    9880             : 
    9881          91 :     const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
    9882          91 :     const char *min_is_inclusive =
    9883          91 :         bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
    9884          91 :     const char *max_is_inclusive =
    9885          91 :         bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
    9886             : 
    9887          91 :     std::unique_ptr<SQLResult> oResultTable;
    9888             :     // Note: for coded domains, we use a little trick by using a dummy
    9889             :     // _{domainname}_domain_description enum that has a single entry whose
    9890             :     // description is the description of the main domain.
    9891             :     {
    9892          91 :         char *pszSQL = sqlite3_mprintf(
    9893             :             "SELECT constraint_type, value, min, %s, "
    9894             :             "max, %s, description, constraint_name "
    9895             :             "FROM gpkg_data_column_constraints "
    9896             :             "WHERE constraint_name IN ('%q', "
    9897             :             "'_%q_domain_description') "
    9898             :             "AND length(constraint_type) < 100 "  // to
    9899             :                                                   // avoid
    9900             :                                                   // denial
    9901             :                                                   // of
    9902             :                                                   // service
    9903             :             "AND (value IS NULL OR length(value) < "
    9904             :             "10000) "  // to avoid denial
    9905             :                        // of service
    9906             :             "AND (description IS NULL OR "
    9907             :             "length(description) < 10000) "  // to
    9908             :                                              // avoid
    9909             :                                              // denial
    9910             :                                              // of
    9911             :                                              // service
    9912             :             "ORDER BY value "
    9913             :             "LIMIT 10000",  // to avoid denial of
    9914             :                             // service
    9915             :             min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
    9916          91 :         oResultTable = SQLQuery(hDB, pszSQL);
    9917          91 :         sqlite3_free(pszSQL);
    9918          91 :         if (!oResultTable)
    9919           0 :             return nullptr;
    9920             :     }
    9921          91 :     if (oResultTable->RowCount() == 0)
    9922             :     {
    9923          33 :         return nullptr;
    9924             :     }
    9925          58 :     if (oResultTable->RowCount() == 10000)
    9926             :     {
    9927           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9928             :                  "Number of rows returned for field domain %s has been "
    9929             :                  "truncated.",
    9930             :                  name.c_str());
    9931             :     }
    9932             : 
    9933             :     // Try to find the field domain data type from fields that implement it
    9934          58 :     int nFieldType = -1;
    9935          58 :     OGRFieldSubType eSubType = OFSTNone;
    9936          58 :     if (HasDataColumnsTable())
    9937             :     {
    9938          53 :         char *pszSQL = sqlite3_mprintf(
    9939             :             "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
    9940             :             "constraint_name = '%q' LIMIT 10",
    9941             :             name.c_str());
    9942         106 :         auto oResultTable2 = SQLQuery(hDB, pszSQL);
    9943          53 :         sqlite3_free(pszSQL);
    9944          53 :         if (oResultTable2 && oResultTable2->RowCount() >= 1)
    9945             :         {
    9946          58 :             for (int iRecord = 0; iRecord < oResultTable2->RowCount();
    9947             :                  iRecord++)
    9948             :             {
    9949          29 :                 const char *pszTableName = oResultTable2->GetValue(0, iRecord);
    9950          29 :                 const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
    9951          29 :                 if (pszTableName == nullptr || pszColumnName == nullptr)
    9952           0 :                     continue;
    9953             :                 OGRLayer *poLayer =
    9954          58 :                     const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
    9955          29 :                         pszTableName);
    9956          29 :                 if (poLayer)
    9957             :                 {
    9958          29 :                     const auto poFDefn = poLayer->GetLayerDefn();
    9959          29 :                     int nIdx = poFDefn->GetFieldIndex(pszColumnName);
    9960          29 :                     if (nIdx >= 0)
    9961             :                     {
    9962          29 :                         const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
    9963          29 :                         const auto eType = poFieldDefn->GetType();
    9964          29 :                         if (nFieldType < 0)
    9965             :                         {
    9966          29 :                             nFieldType = eType;
    9967          29 :                             eSubType = poFieldDefn->GetSubType();
    9968             :                         }
    9969           0 :                         else if ((eType == OFTInteger64 || eType == OFTReal) &&
    9970             :                                  nFieldType == OFTInteger)
    9971             :                         {
    9972             :                             // ok
    9973             :                         }
    9974           0 :                         else if (eType == OFTInteger &&
    9975           0 :                                  (nFieldType == OFTInteger64 ||
    9976             :                                   nFieldType == OFTReal))
    9977             :                         {
    9978           0 :                             nFieldType = OFTInteger;
    9979           0 :                             eSubType = OFSTNone;
    9980             :                         }
    9981           0 :                         else if (nFieldType != eType)
    9982             :                         {
    9983           0 :                             nFieldType = -1;
    9984           0 :                             eSubType = OFSTNone;
    9985           0 :                             break;
    9986             :                         }
    9987             :                     }
    9988             :                 }
    9989             :             }
    9990             :         }
    9991             :     }
    9992             : 
    9993          58 :     std::unique_ptr<OGRFieldDomain> poDomain;
    9994         116 :     std::vector<OGRCodedValue> asValues;
    9995          58 :     bool error = false;
    9996         116 :     CPLString osLastConstraintType;
    9997          58 :     int nFieldTypeFromEnumCode = -1;
    9998         116 :     std::string osConstraintDescription;
    9999         116 :     std::string osDescrConstraintName("_");
   10000          58 :     osDescrConstraintName += name;
   10001          58 :     osDescrConstraintName += "_domain_description";
   10002         145 :     for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
   10003             :     {
   10004          91 :         const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
   10005          91 :         if (pszConstraintType == nullptr)
   10006           2 :             continue;
   10007          91 :         const char *pszValue = oResultTable->GetValue(1, iRecord);
   10008          91 :         const char *pszMin = oResultTable->GetValue(2, iRecord);
   10009             :         const bool bIsMinIncluded =
   10010          91 :             oResultTable->GetValueAsInteger(3, iRecord) == 1;
   10011          91 :         const char *pszMax = oResultTable->GetValue(4, iRecord);
   10012             :         const bool bIsMaxIncluded =
   10013          91 :             oResultTable->GetValueAsInteger(5, iRecord) == 1;
   10014          91 :         const char *pszDescription = oResultTable->GetValue(6, iRecord);
   10015          91 :         const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
   10016             : 
   10017          91 :         if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
   10018             :         {
   10019           1 :             CPLError(CE_Failure, CPLE_AppDefined,
   10020             :                      "Only constraint of type 'enum' can have multiple rows");
   10021           1 :             error = true;
   10022           4 :             break;
   10023             :         }
   10024             : 
   10025          90 :         if (strcmp(pszConstraintType, "enum") == 0)
   10026             :         {
   10027          63 :             if (pszValue == nullptr)
   10028             :             {
   10029           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10030             :                          "NULL in 'value' column of enumeration");
   10031           1 :                 error = true;
   10032           1 :                 break;
   10033             :             }
   10034          62 :             if (osDescrConstraintName == pszConstraintName)
   10035             :             {
   10036           2 :                 if (pszDescription)
   10037             :                 {
   10038           2 :                     osConstraintDescription = pszDescription;
   10039             :                 }
   10040           2 :                 continue;
   10041             :             }
   10042          60 :             if (asValues.empty())
   10043             :             {
   10044          30 :                 asValues.reserve(oResultTable->RowCount() + 1);
   10045             :             }
   10046             :             OGRCodedValue cv;
   10047             :             // intended: the 'value' column in GPKG is actually the code
   10048          60 :             cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
   10049          60 :             if (cv.pszCode == nullptr)
   10050             :             {
   10051           0 :                 error = true;
   10052           0 :                 break;
   10053             :             }
   10054          60 :             if (pszDescription)
   10055             :             {
   10056          48 :                 cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
   10057          48 :                 if (cv.pszValue == nullptr)
   10058             :                 {
   10059           0 :                     VSIFree(cv.pszCode);
   10060           0 :                     error = true;
   10061           0 :                     break;
   10062             :                 }
   10063             :             }
   10064             :             else
   10065             :             {
   10066          12 :                 cv.pszValue = nullptr;
   10067             :             }
   10068             : 
   10069             :             // If we can't get the data type from field definition, guess it
   10070             :             // from code.
   10071          60 :             if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
   10072             :             {
   10073          36 :                 switch (CPLGetValueType(cv.pszCode))
   10074             :                 {
   10075          26 :                     case CPL_VALUE_INTEGER:
   10076             :                     {
   10077          26 :                         if (nFieldTypeFromEnumCode != OFTReal &&
   10078             :                             nFieldTypeFromEnumCode != OFTInteger64)
   10079             :                         {
   10080          18 :                             const auto nVal = CPLAtoGIntBig(cv.pszCode);
   10081          34 :                             if (nVal < std::numeric_limits<int>::min() ||
   10082          16 :                                 nVal > std::numeric_limits<int>::max())
   10083             :                             {
   10084           6 :                                 nFieldTypeFromEnumCode = OFTInteger64;
   10085             :                             }
   10086             :                             else
   10087             :                             {
   10088          12 :                                 nFieldTypeFromEnumCode = OFTInteger;
   10089             :                             }
   10090             :                         }
   10091          26 :                         break;
   10092             :                     }
   10093             : 
   10094           6 :                     case CPL_VALUE_REAL:
   10095           6 :                         nFieldTypeFromEnumCode = OFTReal;
   10096           6 :                         break;
   10097             : 
   10098           4 :                     case CPL_VALUE_STRING:
   10099           4 :                         nFieldTypeFromEnumCode = OFTString;
   10100           4 :                         break;
   10101             :                 }
   10102             :             }
   10103             : 
   10104          60 :             asValues.emplace_back(cv);
   10105             :         }
   10106          27 :         else if (strcmp(pszConstraintType, "range") == 0)
   10107             :         {
   10108             :             OGRField sMin;
   10109             :             OGRField sMax;
   10110          20 :             OGR_RawField_SetUnset(&sMin);
   10111          20 :             OGR_RawField_SetUnset(&sMax);
   10112          20 :             if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
   10113          11 :                 nFieldType = OFTReal;
   10114          39 :             if (pszMin != nullptr &&
   10115          19 :                 CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
   10116             :             {
   10117          15 :                 if (nFieldType == OFTInteger)
   10118           6 :                     sMin.Integer = atoi(pszMin);
   10119           9 :                 else if (nFieldType == OFTInteger64)
   10120           3 :                     sMin.Integer64 = CPLAtoGIntBig(pszMin);
   10121             :                 else /* if( nFieldType == OFTReal ) */
   10122           6 :                     sMin.Real = CPLAtof(pszMin);
   10123             :             }
   10124          39 :             if (pszMax != nullptr &&
   10125          19 :                 CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
   10126             :             {
   10127          15 :                 if (nFieldType == OFTInteger)
   10128           6 :                     sMax.Integer = atoi(pszMax);
   10129           9 :                 else if (nFieldType == OFTInteger64)
   10130           3 :                     sMax.Integer64 = CPLAtoGIntBig(pszMax);
   10131             :                 else /* if( nFieldType == OFTReal ) */
   10132           6 :                     sMax.Real = CPLAtof(pszMax);
   10133             :             }
   10134          20 :             poDomain = std::make_unique<OGRRangeFieldDomain>(
   10135          20 :                 name, pszDescription ? pszDescription : "",
   10136          40 :                 static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
   10137          20 :                 bIsMinIncluded, sMax, bIsMaxIncluded);
   10138             :         }
   10139           7 :         else if (strcmp(pszConstraintType, "glob") == 0)
   10140             :         {
   10141           6 :             if (pszValue == nullptr)
   10142             :             {
   10143           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10144             :                          "NULL in 'value' column of glob");
   10145           1 :                 error = true;
   10146           1 :                 break;
   10147             :             }
   10148           5 :             if (nFieldType < 0)
   10149           1 :                 nFieldType = OFTString;
   10150           5 :             poDomain = std::make_unique<OGRGlobFieldDomain>(
   10151           5 :                 name, pszDescription ? pszDescription : "",
   10152          15 :                 static_cast<OGRFieldType>(nFieldType), eSubType, pszValue);
   10153             :         }
   10154             :         else
   10155             :         {
   10156           1 :             CPLError(CE_Failure, CPLE_AppDefined,
   10157             :                      "Unhandled constraint_type: %s", pszConstraintType);
   10158           1 :             error = true;
   10159           1 :             break;
   10160             :         }
   10161             : 
   10162          85 :         osLastConstraintType = pszConstraintType;
   10163             :     }
   10164             : 
   10165          58 :     if (!asValues.empty())
   10166             :     {
   10167          30 :         if (nFieldType < 0)
   10168          18 :             nFieldType = nFieldTypeFromEnumCode;
   10169          30 :         poDomain = std::make_unique<OGRCodedFieldDomain>(
   10170             :             name, osConstraintDescription,
   10171          60 :             static_cast<OGRFieldType>(nFieldType), eSubType,
   10172          60 :             std::move(asValues));
   10173             :     }
   10174             : 
   10175          58 :     if (error)
   10176             :     {
   10177           4 :         return nullptr;
   10178             :     }
   10179             : 
   10180          54 :     m_oMapFieldDomains[name] = std::move(poDomain);
   10181          54 :     return GDALDataset::GetFieldDomain(name);
   10182             : }
   10183             : 
   10184             : /************************************************************************/
   10185             : /*                           AddFieldDomain()                           */
   10186             : /************************************************************************/
   10187             : 
   10188          19 : bool GDALGeoPackageDataset::AddFieldDomain(
   10189             :     std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
   10190             : {
   10191          38 :     const std::string domainName(domain->GetName());
   10192          19 :     if (!GetUpdate())
   10193             :     {
   10194           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10195             :                  "AddFieldDomain() not supported on read-only dataset");
   10196           0 :         return false;
   10197             :     }
   10198          19 :     if (GetFieldDomain(domainName) != nullptr)
   10199             :     {
   10200           1 :         failureReason = "A domain of identical name already exists";
   10201           1 :         return false;
   10202             :     }
   10203          18 :     if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
   10204           0 :         return false;
   10205             : 
   10206          18 :     const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
   10207          18 :     const char *min_is_inclusive =
   10208          18 :         bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
   10209          18 :     const char *max_is_inclusive =
   10210          18 :         bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
   10211             : 
   10212          18 :     const auto &osDescription = domain->GetDescription();
   10213          18 :     switch (domain->GetDomainType())
   10214             :     {
   10215          11 :         case OFDT_CODED:
   10216             :         {
   10217             :             const auto poCodedDomain =
   10218          11 :                 cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
   10219          11 :             if (!osDescription.empty())
   10220             :             {
   10221             :                 // We use a little trick by using a dummy
   10222             :                 // _{domainname}_domain_description enum that has a single
   10223             :                 // entry whose description is the description of the main
   10224             :                 // domain.
   10225           1 :                 char *pszSQL = sqlite3_mprintf(
   10226             :                     "INSERT INTO gpkg_data_column_constraints ("
   10227             :                     "constraint_name, constraint_type, value, "
   10228             :                     "min, %s, max, %s, "
   10229             :                     "description) VALUES ("
   10230             :                     "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
   10231             :                     "NULL, %Q)",
   10232             :                     min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10233             :                     osDescription.c_str());
   10234           1 :                 CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
   10235           1 :                 sqlite3_free(pszSQL);
   10236             :             }
   10237          11 :             const auto &enumeration = poCodedDomain->GetEnumeration();
   10238          33 :             for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
   10239             :             {
   10240          22 :                 char *pszSQL = sqlite3_mprintf(
   10241             :                     "INSERT INTO gpkg_data_column_constraints ("
   10242             :                     "constraint_name, constraint_type, value, "
   10243             :                     "min, %s, max, %s, "
   10244             :                     "description) VALUES ("
   10245             :                     "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
   10246             :                     min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10247          22 :                     enumeration[i].pszCode, enumeration[i].pszValue);
   10248          22 :                 bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
   10249          22 :                 sqlite3_free(pszSQL);
   10250          22 :                 if (!ok)
   10251           0 :                     return false;
   10252             :             }
   10253          11 :             break;
   10254             :         }
   10255             : 
   10256           6 :         case OFDT_RANGE:
   10257             :         {
   10258             :             const auto poRangeDomain =
   10259           6 :                 cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
   10260           6 :             const auto eFieldType = poRangeDomain->GetFieldType();
   10261           6 :             if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
   10262             :                 eFieldType != OFTReal)
   10263             :             {
   10264             :                 failureReason = "Only range domains of numeric type are "
   10265           0 :                                 "supported in GeoPackage";
   10266           0 :                 return false;
   10267             :             }
   10268             : 
   10269           6 :             double dfMin = -std::numeric_limits<double>::infinity();
   10270           6 :             double dfMax = std::numeric_limits<double>::infinity();
   10271           6 :             bool bMinIsInclusive = true;
   10272           6 :             const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
   10273           6 :             bool bMaxIsInclusive = true;
   10274           6 :             const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
   10275           6 :             if (eFieldType == OFTInteger)
   10276             :             {
   10277           2 :                 if (!OGR_RawField_IsUnset(&sMin))
   10278           2 :                     dfMin = sMin.Integer;
   10279           2 :                 if (!OGR_RawField_IsUnset(&sMax))
   10280           2 :                     dfMax = sMax.Integer;
   10281             :             }
   10282           4 :             else if (eFieldType == OFTInteger64)
   10283             :             {
   10284           1 :                 if (!OGR_RawField_IsUnset(&sMin))
   10285           1 :                     dfMin = static_cast<double>(sMin.Integer64);
   10286           1 :                 if (!OGR_RawField_IsUnset(&sMax))
   10287           1 :                     dfMax = static_cast<double>(sMax.Integer64);
   10288             :             }
   10289             :             else /* if( eFieldType == OFTReal ) */
   10290             :             {
   10291           3 :                 if (!OGR_RawField_IsUnset(&sMin))
   10292           3 :                     dfMin = sMin.Real;
   10293           3 :                 if (!OGR_RawField_IsUnset(&sMax))
   10294           3 :                     dfMax = sMax.Real;
   10295             :             }
   10296             : 
   10297           6 :             sqlite3_stmt *hInsertStmt = nullptr;
   10298             :             const char *pszSQL =
   10299           6 :                 CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
   10300             :                            "constraint_name, constraint_type, value, "
   10301             :                            "min, %s, max, %s, "
   10302             :                            "description) VALUES ("
   10303             :                            "?, 'range', NULL, ?, ?, ?, ?, ?)",
   10304             :                            min_is_inclusive, max_is_inclusive);
   10305           6 :             if (SQLPrepareWithError(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
   10306             :                 SQLITE_OK)
   10307             :             {
   10308           0 :                 return false;
   10309             :             }
   10310           6 :             sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
   10311           6 :                               static_cast<int>(domainName.size()),
   10312             :                               SQLITE_TRANSIENT);
   10313           6 :             sqlite3_bind_double(hInsertStmt, 2, dfMin);
   10314           6 :             sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
   10315           6 :             sqlite3_bind_double(hInsertStmt, 4, dfMax);
   10316           6 :             sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
   10317           6 :             if (osDescription.empty())
   10318             :             {
   10319           3 :                 sqlite3_bind_null(hInsertStmt, 6);
   10320             :             }
   10321             :             else
   10322             :             {
   10323           3 :                 sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
   10324           3 :                                   static_cast<int>(osDescription.size()),
   10325             :                                   SQLITE_TRANSIENT);
   10326             :             }
   10327           6 :             const int sqlite_err = sqlite3_step(hInsertStmt);
   10328           6 :             sqlite3_finalize(hInsertStmt);
   10329           6 :             if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
   10330             :             {
   10331           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10332             :                          "failed to execute insertion '%s': %s", pszSQL,
   10333             :                          sqlite3_errmsg(hDB));
   10334           0 :                 return false;
   10335             :             }
   10336             : 
   10337           6 :             break;
   10338             :         }
   10339             : 
   10340           1 :         case OFDT_GLOB:
   10341             :         {
   10342             :             const auto poGlobDomain =
   10343           1 :                 cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
   10344           2 :             char *pszSQL = sqlite3_mprintf(
   10345             :                 "INSERT INTO gpkg_data_column_constraints ("
   10346             :                 "constraint_name, constraint_type, value, "
   10347             :                 "min, %s, max, %s, "
   10348             :                 "description) VALUES ("
   10349             :                 "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
   10350             :                 min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10351           1 :                 poGlobDomain->GetGlob().c_str(),
   10352           2 :                 osDescription.empty() ? nullptr : osDescription.c_str());
   10353           1 :             bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
   10354           1 :             sqlite3_free(pszSQL);
   10355           1 :             if (!ok)
   10356           0 :                 return false;
   10357             : 
   10358           1 :             break;
   10359             :         }
   10360             :     }
   10361             : 
   10362          18 :     m_oMapFieldDomains[domainName] = std::move(domain);
   10363          18 :     return true;
   10364             : }
   10365             : 
   10366             : /************************************************************************/
   10367             : /*                         UpdateFieldDomain()                          */
   10368             : /************************************************************************/
   10369             : 
   10370           3 : bool GDALGeoPackageDataset::UpdateFieldDomain(
   10371             :     std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
   10372             : {
   10373           6 :     const std::string domainName(domain->GetName());
   10374           3 :     if (eAccess != GA_Update)
   10375             :     {
   10376           1 :         CPLError(CE_Failure, CPLE_NotSupported,
   10377             :                  "UpdateFieldDomain() not supported on read-only dataset");
   10378           1 :         return false;
   10379             :     }
   10380             : 
   10381           2 :     if (GetFieldDomain(domainName) == nullptr)
   10382             :     {
   10383           1 :         failureReason = "The domain should already exist to be updated";
   10384           1 :         return false;
   10385             :     }
   10386             : 
   10387           1 :     bool bRet = SoftStartTransaction() == OGRERR_NONE;
   10388           1 :     if (bRet)
   10389             :     {
   10390           2 :         bRet = DeleteFieldDomain(domainName, failureReason) &&
   10391           1 :                AddFieldDomain(std::move(domain), failureReason);
   10392           1 :         if (bRet)
   10393           1 :             bRet = SoftCommitTransaction() == OGRERR_NONE;
   10394             :         else
   10395           0 :             SoftRollbackTransaction();
   10396             :     }
   10397           1 :     return bRet;
   10398             : }
   10399             : 
   10400             : /************************************************************************/
   10401             : /*                         DeleteFieldDomain()                          */
   10402             : /************************************************************************/
   10403             : 
   10404          18 : bool GDALGeoPackageDataset::DeleteFieldDomain(const std::string &name,
   10405             :                                               std::string &failureReason)
   10406             : {
   10407          18 :     if (eAccess != GA_Update)
   10408             :     {
   10409           1 :         CPLError(CE_Failure, CPLE_NotSupported,
   10410             :                  "DeleteFieldDomain() not supported on read-only dataset");
   10411           1 :         return false;
   10412             :     }
   10413          17 :     if (GetFieldDomain(name) == nullptr)
   10414             :     {
   10415           1 :         failureReason = "Domain does not exist";
   10416           1 :         return false;
   10417             :     }
   10418             : 
   10419             :     char *pszSQL =
   10420          16 :         sqlite3_mprintf("DELETE FROM gpkg_data_column_constraints WHERE "
   10421             :                         "constraint_name IN ('%q', '_%q_domain_description')",
   10422             :                         name.c_str(), name.c_str());
   10423          16 :     const bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
   10424          16 :     sqlite3_free(pszSQL);
   10425          16 :     if (ok)
   10426          16 :         m_oMapFieldDomains.erase(name);
   10427          16 :     return ok;
   10428             : }
   10429             : 
   10430             : /************************************************************************/
   10431             : /*                          AddRelationship()                           */
   10432             : /************************************************************************/
   10433             : 
   10434          24 : bool GDALGeoPackageDataset::AddRelationship(
   10435             :     std::unique_ptr<GDALRelationship> &&relationship,
   10436             :     std::string &failureReason)
   10437             : {
   10438          24 :     if (!GetUpdate())
   10439             :     {
   10440           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10441             :                  "AddRelationship() not supported on read-only dataset");
   10442           0 :         return false;
   10443             :     }
   10444             : 
   10445             :     const std::string osRelationshipName = GenerateNameForRelationship(
   10446          24 :         relationship->GetLeftTableName().c_str(),
   10447          24 :         relationship->GetRightTableName().c_str(),
   10448          96 :         relationship->GetRelatedTableType().c_str());
   10449             :     // sanity checks
   10450          24 :     if (GetRelationship(osRelationshipName) != nullptr)
   10451             :     {
   10452           1 :         failureReason = "A relationship of identical name already exists";
   10453           1 :         return false;
   10454             :     }
   10455             : 
   10456          23 :     if (!ValidateRelationship(relationship.get(), failureReason))
   10457             :     {
   10458          14 :         return false;
   10459             :     }
   10460             : 
   10461           9 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
   10462             :     {
   10463           0 :         return false;
   10464             :     }
   10465           9 :     if (!CreateRelationsTableIfNecessary())
   10466             :     {
   10467           0 :         failureReason = "Could not create gpkgext_relations table";
   10468           0 :         return false;
   10469             :     }
   10470           9 :     if (SQLGetInteger(GetDB(),
   10471             :                       "SELECT 1 FROM gpkg_extensions WHERE "
   10472             :                       "table_name = 'gpkgext_relations'",
   10473           9 :                       nullptr) != 1)
   10474             :     {
   10475           4 :         if (OGRERR_NONE !=
   10476           4 :             SQLCommand(
   10477             :                 GetDB(),
   10478             :                 "INSERT INTO gpkg_extensions "
   10479             :                 "(table_name,column_name,extension_name,definition,scope) "
   10480             :                 "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
   10481             :                 "'http://www.geopackage.org/18-000.html', "
   10482             :                 "'read-write')"))
   10483             :         {
   10484             :             failureReason =
   10485           0 :                 "Could not create gpkg_extensions entry for gpkgext_relations";
   10486           0 :             return false;
   10487             :         }
   10488             :     }
   10489             : 
   10490           9 :     const std::string &osLeftTableName = relationship->GetLeftTableName();
   10491           9 :     const std::string &osRightTableName = relationship->GetRightTableName();
   10492           9 :     const auto &aosLeftTableFields = relationship->GetLeftTableFields();
   10493           9 :     const auto &aosRightTableFields = relationship->GetRightTableFields();
   10494             : 
   10495          18 :     std::string osRelatedTableType = relationship->GetRelatedTableType();
   10496           9 :     if (osRelatedTableType.empty())
   10497             :     {
   10498           5 :         osRelatedTableType = "features";
   10499             :     }
   10500             : 
   10501             :     // generate mapping table if not set
   10502          18 :     CPLString osMappingTableName = relationship->GetMappingTableName();
   10503           9 :     if (osMappingTableName.empty())
   10504             :     {
   10505           3 :         int nIndex = 1;
   10506           3 :         osMappingTableName = osLeftTableName + "_" + osRightTableName;
   10507           3 :         while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
   10508             :         {
   10509           0 :             nIndex += 1;
   10510             :             osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
   10511           0 :                                       osRightTableName.c_str(), nIndex);
   10512             :         }
   10513             : 
   10514             :         // determine whether base/related keys are unique
   10515           3 :         bool bBaseKeyIsUnique = false;
   10516             :         {
   10517             :             const std::set<std::string> uniqueBaseFieldsUC =
   10518             :                 SQLGetUniqueFieldUCConstraints(GetDB(),
   10519           6 :                                                osLeftTableName.c_str());
   10520           6 :             if (uniqueBaseFieldsUC.find(
   10521           3 :                     CPLString(aosLeftTableFields[0]).toupper()) !=
   10522           6 :                 uniqueBaseFieldsUC.end())
   10523             :             {
   10524           2 :                 bBaseKeyIsUnique = true;
   10525             :             }
   10526             :         }
   10527           3 :         bool bRelatedKeyIsUnique = false;
   10528             :         {
   10529             :             const std::set<std::string> uniqueRelatedFieldsUC =
   10530             :                 SQLGetUniqueFieldUCConstraints(GetDB(),
   10531           6 :                                                osRightTableName.c_str());
   10532           6 :             if (uniqueRelatedFieldsUC.find(
   10533           3 :                     CPLString(aosRightTableFields[0]).toupper()) !=
   10534           6 :                 uniqueRelatedFieldsUC.end())
   10535             :             {
   10536           2 :                 bRelatedKeyIsUnique = true;
   10537             :             }
   10538             :         }
   10539             : 
   10540             :         // create mapping table
   10541             : 
   10542           3 :         std::string osBaseIdDefinition = "base_id INTEGER";
   10543           3 :         if (bBaseKeyIsUnique)
   10544             :         {
   10545           2 :             char *pszSQL = sqlite3_mprintf(
   10546             :                 " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
   10547             :                 "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
   10548             :                 "DEFERRED",
   10549             :                 osMappingTableName.c_str(), osLeftTableName.c_str(),
   10550           2 :                 aosLeftTableFields[0].c_str());
   10551           2 :             osBaseIdDefinition += pszSQL;
   10552           2 :             sqlite3_free(pszSQL);
   10553             :         }
   10554             : 
   10555           3 :         std::string osRelatedIdDefinition = "related_id INTEGER";
   10556           3 :         if (bRelatedKeyIsUnique)
   10557             :         {
   10558           2 :             char *pszSQL = sqlite3_mprintf(
   10559             :                 " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
   10560             :                 "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
   10561             :                 "DEFERRED",
   10562             :                 osMappingTableName.c_str(), osRightTableName.c_str(),
   10563           2 :                 aosRightTableFields[0].c_str());
   10564           2 :             osRelatedIdDefinition += pszSQL;
   10565           2 :             sqlite3_free(pszSQL);
   10566             :         }
   10567             : 
   10568           3 :         char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
   10569             :                                        "id INTEGER PRIMARY KEY AUTOINCREMENT, "
   10570             :                                        "%s, %s);",
   10571             :                                        osMappingTableName.c_str(),
   10572             :                                        osBaseIdDefinition.c_str(),
   10573             :                                        osRelatedIdDefinition.c_str());
   10574           3 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
   10575           3 :         sqlite3_free(pszSQL);
   10576           3 :         if (eErr != OGRERR_NONE)
   10577             :         {
   10578             :             failureReason =
   10579           0 :                 ("Could not create mapping table " + osMappingTableName)
   10580           0 :                     .c_str();
   10581           0 :             return false;
   10582             :         }
   10583             : 
   10584             :         /*
   10585             :          * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
   10586             :          * The related tables extension explicitly states that the mapping table should only be
   10587             :          * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
   10588             :          * https://github.com/opengeospatial/geopackage/issues/679).
   10589             :          *
   10590             :          * However, if we don't insert the mapping table into gpkg_contents then it is no longer
   10591             :          * visible to some clients (eg ESRI software only allows opening tables that are present
   10592             :          * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
   10593             :          *
   10594             :          * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
   10595             :          */
   10596           3 :         pszSQL = sqlite3_mprintf(
   10597             :             "INSERT INTO gpkg_contents "
   10598             :             "(table_name,data_type,identifier,description,last_change,srs_id) "
   10599             :             "VALUES "
   10600             :             "('%q','attributes','%q','Mapping table for relationship between "
   10601             :             "%q and %q',%s,0)",
   10602             :             osMappingTableName.c_str(), /*table_name*/
   10603             :             osMappingTableName.c_str(), /*identifier*/
   10604             :             osLeftTableName.c_str(),    /*description left table name*/
   10605             :             osRightTableName.c_str(),   /*description right table name*/
   10606           6 :             GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
   10607             : 
   10608             :         // Note -- we explicitly ignore failures here, because hey, we aren't really
   10609             :         // supposed to be adding this table to gpkg_contents anyway!
   10610           3 :         (void)SQLCommand(hDB, pszSQL);
   10611           3 :         sqlite3_free(pszSQL);
   10612             : 
   10613           3 :         pszSQL = sqlite3_mprintf(
   10614             :             "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
   10615             :             osMappingTableName.c_str(), osMappingTableName.c_str());
   10616           3 :         eErr = SQLCommand(hDB, pszSQL);
   10617           3 :         sqlite3_free(pszSQL);
   10618           3 :         if (eErr != OGRERR_NONE)
   10619             :         {
   10620           0 :             failureReason = ("Could not create index for " +
   10621           0 :                              osMappingTableName + " (base_id)")
   10622           0 :                                 .c_str();
   10623           0 :             return false;
   10624             :         }
   10625             : 
   10626           3 :         pszSQL = sqlite3_mprintf(
   10627             :             "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
   10628             :             osMappingTableName.c_str(), osMappingTableName.c_str());
   10629           3 :         eErr = SQLCommand(hDB, pszSQL);
   10630           3 :         sqlite3_free(pszSQL);
   10631           3 :         if (eErr != OGRERR_NONE)
   10632             :         {
   10633           0 :             failureReason = ("Could not create index for " +
   10634           0 :                              osMappingTableName + " (related_id)")
   10635           0 :                                 .c_str();
   10636           0 :             return false;
   10637             :         }
   10638             :     }
   10639             :     else
   10640             :     {
   10641             :         // validate mapping table structure
   10642           6 :         if (OGRGeoPackageTableLayer *poLayer =
   10643           6 :                 cpl::down_cast<OGRGeoPackageTableLayer *>(
   10644           6 :                     GetLayerByName(osMappingTableName)))
   10645             :         {
   10646           4 :             if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
   10647             :             {
   10648             :                 failureReason =
   10649           2 :                     ("Field base_id must exist in " + osMappingTableName)
   10650           1 :                         .c_str();
   10651           1 :                 return false;
   10652             :             }
   10653           3 :             if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
   10654             :             {
   10655             :                 failureReason =
   10656           2 :                     ("Field related_id must exist in " + osMappingTableName)
   10657           1 :                         .c_str();
   10658           1 :                 return false;
   10659             :             }
   10660             :         }
   10661             :         else
   10662             :         {
   10663             :             failureReason =
   10664           2 :                 ("Could not retrieve table " + osMappingTableName).c_str();
   10665           2 :             return false;
   10666             :         }
   10667             :     }
   10668             : 
   10669           5 :     char *pszSQL = sqlite3_mprintf(
   10670             :         "INSERT INTO gpkg_extensions "
   10671             :         "(table_name,column_name,extension_name,definition,scope) "
   10672             :         "VALUES ('%q', NULL, 'gpkg_related_tables', "
   10673             :         "'http://www.geopackage.org/18-000.html', "
   10674             :         "'read-write')",
   10675             :         osMappingTableName.c_str());
   10676           5 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
   10677           5 :     sqlite3_free(pszSQL);
   10678           5 :     if (eErr != OGRERR_NONE)
   10679             :     {
   10680           0 :         failureReason = ("Could not insert mapping table " +
   10681           0 :                          osMappingTableName + " into gpkg_extensions")
   10682           0 :                             .c_str();
   10683           0 :         return false;
   10684             :     }
   10685             : 
   10686          15 :     pszSQL = sqlite3_mprintf(
   10687             :         "INSERT INTO gpkgext_relations "
   10688             :         "(base_table_name,base_primary_column,related_table_name,related_"
   10689             :         "primary_column,relation_name,mapping_table_name) "
   10690             :         "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
   10691           5 :         osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
   10692           5 :         osRightTableName.c_str(), aosRightTableFields[0].c_str(),
   10693             :         osRelatedTableType.c_str(), osMappingTableName.c_str());
   10694           5 :     eErr = SQLCommand(hDB, pszSQL);
   10695           5 :     sqlite3_free(pszSQL);
   10696           5 :     if (eErr != OGRERR_NONE)
   10697             :     {
   10698           0 :         failureReason = "Could not insert relationship into gpkgext_relations";
   10699           0 :         return false;
   10700             :     }
   10701             : 
   10702           5 :     ClearCachedRelationships();
   10703           5 :     LoadRelationships();
   10704           5 :     return true;
   10705             : }
   10706             : 
   10707             : /************************************************************************/
   10708             : /*                         DeleteRelationship()                         */
   10709             : /************************************************************************/
   10710             : 
   10711           4 : bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
   10712             :                                                std::string &failureReason)
   10713             : {
   10714           4 :     if (eAccess != GA_Update)
   10715             :     {
   10716           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10717             :                  "DeleteRelationship() not supported on read-only dataset");
   10718           0 :         return false;
   10719             :     }
   10720             : 
   10721             :     // ensure relationships are up to date before we try to remove one
   10722           4 :     ClearCachedRelationships();
   10723           4 :     LoadRelationships();
   10724             : 
   10725           8 :     std::string osMappingTableName;
   10726             :     {
   10727           4 :         const GDALRelationship *poRelationship = GetRelationship(name);
   10728           4 :         if (poRelationship == nullptr)
   10729             :         {
   10730           1 :             failureReason = "Could not find relationship with name " + name;
   10731           1 :             return false;
   10732             :         }
   10733             : 
   10734           3 :         osMappingTableName = poRelationship->GetMappingTableName();
   10735             :     }
   10736             : 
   10737             :     // DeleteLayerCommon will delete existing relationship objects, so we can't
   10738             :     // refer to poRelationship or any of its members previously obtained here
   10739           3 :     if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
   10740             :     {
   10741             :         failureReason =
   10742           0 :             "Could not remove mapping layer name " + osMappingTableName;
   10743             : 
   10744             :         // relationships may have been left in an inconsistent state -- reload
   10745             :         // them now
   10746           0 :         ClearCachedRelationships();
   10747           0 :         LoadRelationships();
   10748           0 :         return false;
   10749             :     }
   10750             : 
   10751           3 :     ClearCachedRelationships();
   10752           3 :     LoadRelationships();
   10753           3 :     return true;
   10754             : }
   10755             : 
   10756             : /************************************************************************/
   10757             : /*                         UpdateRelationship()                         */
   10758             : /************************************************************************/
   10759             : 
   10760           6 : bool GDALGeoPackageDataset::UpdateRelationship(
   10761             :     std::unique_ptr<GDALRelationship> &&relationship,
   10762             :     std::string &failureReason)
   10763             : {
   10764           6 :     if (eAccess != GA_Update)
   10765             :     {
   10766           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10767             :                  "UpdateRelationship() not supported on read-only dataset");
   10768           0 :         return false;
   10769             :     }
   10770             : 
   10771             :     // ensure relationships are up to date before we try to update one
   10772           6 :     ClearCachedRelationships();
   10773           6 :     LoadRelationships();
   10774             : 
   10775           6 :     const std::string &osRelationshipName = relationship->GetName();
   10776           6 :     const std::string &osLeftTableName = relationship->GetLeftTableName();
   10777           6 :     const std::string &osRightTableName = relationship->GetRightTableName();
   10778           6 :     const std::string &osMappingTableName = relationship->GetMappingTableName();
   10779           6 :     const auto &aosLeftTableFields = relationship->GetLeftTableFields();
   10780           6 :     const auto &aosRightTableFields = relationship->GetRightTableFields();
   10781             : 
   10782             :     // sanity checks
   10783             :     {
   10784             :         const GDALRelationship *poExistingRelationship =
   10785           6 :             GetRelationship(osRelationshipName);
   10786           6 :         if (poExistingRelationship == nullptr)
   10787             :         {
   10788             :             failureReason =
   10789           1 :                 "The relationship should already exist to be updated";
   10790           1 :             return false;
   10791             :         }
   10792             : 
   10793           5 :         if (!ValidateRelationship(relationship.get(), failureReason))
   10794             :         {
   10795           2 :             return false;
   10796             :         }
   10797             : 
   10798             :         // we don't permit changes to the participating tables
   10799           3 :         if (osLeftTableName != poExistingRelationship->GetLeftTableName())
   10800             :         {
   10801           0 :             failureReason = ("Cannot change base table from " +
   10802           0 :                              poExistingRelationship->GetLeftTableName() +
   10803           0 :                              " to " + osLeftTableName)
   10804           0 :                                 .c_str();
   10805           0 :             return false;
   10806             :         }
   10807           3 :         if (osRightTableName != poExistingRelationship->GetRightTableName())
   10808             :         {
   10809           0 :             failureReason = ("Cannot change related table from " +
   10810           0 :                              poExistingRelationship->GetRightTableName() +
   10811           0 :                              " to " + osRightTableName)
   10812           0 :                                 .c_str();
   10813           0 :             return false;
   10814             :         }
   10815           3 :         if (osMappingTableName != poExistingRelationship->GetMappingTableName())
   10816             :         {
   10817           0 :             failureReason = ("Cannot change mapping table from " +
   10818           0 :                              poExistingRelationship->GetMappingTableName() +
   10819           0 :                              " to " + osMappingTableName)
   10820           0 :                                 .c_str();
   10821           0 :             return false;
   10822             :         }
   10823             :     }
   10824             : 
   10825           6 :     std::string osRelatedTableType = relationship->GetRelatedTableType();
   10826           3 :     if (osRelatedTableType.empty())
   10827             :     {
   10828           0 :         osRelatedTableType = "features";
   10829             :     }
   10830             : 
   10831           3 :     char *pszSQL = sqlite3_mprintf(
   10832             :         "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
   10833             :         osMappingTableName.c_str());
   10834           3 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
   10835           3 :     sqlite3_free(pszSQL);
   10836           3 :     if (eErr != OGRERR_NONE)
   10837             :     {
   10838             :         failureReason =
   10839           0 :             "Could not delete old relationship from gpkgext_relations";
   10840           0 :         return false;
   10841             :     }
   10842             : 
   10843           9 :     pszSQL = sqlite3_mprintf(
   10844             :         "INSERT INTO gpkgext_relations "
   10845             :         "(base_table_name,base_primary_column,related_table_name,related_"
   10846             :         "primary_column,relation_name,mapping_table_name) "
   10847             :         "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
   10848           3 :         osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
   10849           3 :         osRightTableName.c_str(), aosRightTableFields[0].c_str(),
   10850             :         osRelatedTableType.c_str(), osMappingTableName.c_str());
   10851           3 :     eErr = SQLCommand(hDB, pszSQL);
   10852           3 :     sqlite3_free(pszSQL);
   10853           3 :     if (eErr != OGRERR_NONE)
   10854             :     {
   10855             :         failureReason =
   10856           0 :             "Could not insert updated relationship into gpkgext_relations";
   10857           0 :         return false;
   10858             :     }
   10859             : 
   10860           3 :     ClearCachedRelationships();
   10861           3 :     LoadRelationships();
   10862           3 :     return true;
   10863             : }
   10864             : 
   10865             : /************************************************************************/
   10866             : /*                       GetSqliteMasterContent()                       */
   10867             : /************************************************************************/
   10868             : 
   10869             : const std::vector<SQLSqliteMasterContent> &
   10870           2 : GDALGeoPackageDataset::GetSqliteMasterContent()
   10871             : {
   10872           2 :     if (m_aoSqliteMasterContent.empty())
   10873             :     {
   10874             :         auto oResultTable =
   10875           2 :             SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
   10876           1 :         if (oResultTable)
   10877             :         {
   10878          58 :             for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
   10879             :             {
   10880         114 :                 SQLSqliteMasterContent row;
   10881          57 :                 const char *pszSQL = oResultTable->GetValue(0, rowCnt);
   10882          57 :                 row.osSQL = pszSQL ? pszSQL : "";
   10883          57 :                 const char *pszType = oResultTable->GetValue(1, rowCnt);
   10884          57 :                 row.osType = pszType ? pszType : "";
   10885          57 :                 const char *pszTableName = oResultTable->GetValue(2, rowCnt);
   10886          57 :                 row.osTableName = pszTableName ? pszTableName : "";
   10887          57 :                 m_aoSqliteMasterContent.emplace_back(std::move(row));
   10888             :             }
   10889             :         }
   10890             :     }
   10891           2 :     return m_aoSqliteMasterContent;
   10892             : }

Generated by: LCOV version 1.14