LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gpkg - ogrgeopackagedatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 4094 4573 89.5 %
Date: 2025-02-18 14:19:29 Functions: 136 136 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GeoPackage Translator
       4             :  * Purpose:  Implements GDALGeoPackageDataset class
       5             :  * Author:   Paul Ramsey <pramsey@boundlessgeo.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2013, Paul Ramsey <pramsey@boundlessgeo.com>
       9             :  * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "ogr_geopackage.h"
      15             : #include "ogr_p.h"
      16             : #include "ogr_swq.h"
      17             : #include "gdalwarper.h"
      18             : #include "gdal_utils.h"
      19             : #include "ogrgeopackageutility.h"
      20             : #include "ogrsqliteutility.h"
      21             : #include "ogr_wkb.h"
      22             : #include "vrt/vrtdataset.h"
      23             : 
      24             : #include "tilematrixset.hpp"
      25             : 
      26             : #include <cstdlib>
      27             : 
      28             : #include <algorithm>
      29             : #include <limits>
      30             : #include <sstream>
      31             : 
      32             : #define COMPILATION_ALLOWED
      33             : #define DEFINE_OGRSQLiteSQLFunctionsSetCaseSensitiveLike
      34             : #include "ogrsqlitesqlfunctionscommon.cpp"
      35             : 
      36             : // Keep in sync prototype of those 2 functions between gdalopeninfo.cpp,
      37             : // ogrsqlitedatasource.cpp and ogrgeopackagedatasource.cpp
      38             : void GDALOpenInfoDeclareFileNotToOpen(const char *pszFilename,
      39             :                                       const GByte *pabyHeader,
      40             :                                       int nHeaderBytes);
      41             : void GDALOpenInfoUnDeclareFileNotToOpen(const char *pszFilename);
      42             : 
      43             : /************************************************************************/
      44             : /*                             Tiling schemes                           */
      45             : /************************************************************************/
      46             : 
      47             : typedef struct
      48             : {
      49             :     const char *pszName;
      50             :     int nEPSGCode;
      51             :     double dfMinX;
      52             :     double dfMaxY;
      53             :     int nTileXCountZoomLevel0;
      54             :     int nTileYCountZoomLevel0;
      55             :     int nTileWidth;
      56             :     int nTileHeight;
      57             :     double dfPixelXSizeZoomLevel0;
      58             :     double dfPixelYSizeZoomLevel0;
      59             : } TilingSchemeDefinition;
      60             : 
      61             : static const TilingSchemeDefinition asTilingSchemes[] = {
      62             :     /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
      63             :        Annex E.3 */
      64             :     {"GoogleCRS84Quad", 4326, -180.0, 180.0, 1, 1, 256, 256, 360.0 / 256,
      65             :      360.0 / 256},
      66             : 
      67             :     /* See global-mercator at
      68             :        http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
      69             :     {"PseudoTMS_GlobalMercator", 3857, -20037508.34, 20037508.34, 2, 2, 256,
      70             :      256, 78271.516, 78271.516},
      71             : };
      72             : 
      73             : // Setting it above 30 would lead to integer overflow ((1 << 31) > INT_MAX)
      74             : constexpr int MAX_ZOOM_LEVEL = 30;
      75             : 
      76             : /************************************************************************/
      77             : /*                     GetTilingScheme()                                */
      78             : /************************************************************************/
      79             : 
      80             : static std::unique_ptr<TilingSchemeDefinition>
      81         553 : GetTilingScheme(const char *pszName)
      82             : {
      83         553 :     if (EQUAL(pszName, "CUSTOM"))
      84         425 :         return nullptr;
      85             : 
      86         256 :     for (const auto &tilingScheme : asTilingSchemes)
      87             :     {
      88         195 :         if (EQUAL(pszName, tilingScheme.pszName))
      89             :         {
      90             :             return std::unique_ptr<TilingSchemeDefinition>(
      91          67 :                 new 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         832 : OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
     192             : {
     193         832 :     CPLAssert(hDB != nullptr);
     194             : 
     195         832 :     const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
     196             :                                                 "PRAGMA user_version = %u",
     197             :                                                 m_nApplicationId,
     198        1664 :                                                 m_nUserVersion));
     199        1664 :     return SQLCommand(hDB, osPragma.c_str());
     200             : }
     201             : 
     202        2359 : bool GDALGeoPackageDataset::CloseDB()
     203             : {
     204        2359 :     OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
     205        2359 :     m_pSQLFunctionData = nullptr;
     206        2359 :     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         760 : static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
     223             :                                      int nEPSGCode)
     224             : {
     225         760 :     CPLPushErrorHandler(CPLQuietErrorHandler);
     226         760 :     const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
     227         760 :     CPLPopErrorHandler();
     228         760 :     CPLErrorReset();
     229         760 :     return eErr;
     230             : }
     231             : 
     232             : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
     233        1170 : GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
     234             :                                      bool bEmitErrorIfNotFound)
     235             : {
     236        1170 :     const auto oIter = m_oMapSrsIdToSrs.find(iSrsId);
     237        1170 :     if (oIter != m_oMapSrsIdToSrs.end())
     238             :     {
     239          80 :         if (oIter->second == nullptr)
     240          31 :             return nullptr;
     241          49 :         oIter->second->Reference();
     242             :         return std::unique_ptr<OGRSpatialReference,
     243          49 :                                OGRSpatialReferenceReleaser>(oIter->second);
     244             :     }
     245             : 
     246        1090 :     if (iSrsId == 0 || iSrsId == -1)
     247             :     {
     248         120 :         OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
     249         120 :         poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     250             : 
     251             :         // See corresponding tests in GDALGeoPackageDataset::GetSrsId
     252         120 :         if (iSrsId == 0)
     253             :         {
     254          29 :             poSpatialRef->SetGeogCS("Undefined geographic SRS", "unknown",
     255             :                                     "unknown", SRS_WGS84_SEMIMAJOR,
     256             :                                     SRS_WGS84_INVFLATTENING);
     257             :         }
     258          91 :         else if (iSrsId == -1)
     259             :         {
     260          91 :             poSpatialRef->SetLocalCS("Undefined Cartesian SRS");
     261          91 :             poSpatialRef->SetLinearUnits(SRS_UL_METER, 1.0);
     262             :         }
     263             : 
     264         120 :         m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
     265         120 :         poSpatialRef->Reference();
     266             :         return std::unique_ptr<OGRSpatialReference,
     267         120 :                                OGRSpatialReferenceReleaser>(poSpatialRef);
     268             :     }
     269             : 
     270        1940 :     CPLString oSQL;
     271         970 :     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         970 :                 m_bHasDefinition12_063 ? ", definition_12_063" : "",
     276         970 :                 m_bHasEpochColumn ? ", epoch" : "", iSrsId);
     277             : 
     278        1940 :     auto oResult = SQLQuery(hDB, oSQL.c_str());
     279             : 
     280         970 :     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         958 :     const char *pszName = oResult->GetValue(0, 0);
     307         958 :     if (pszName && EQUAL(pszName, "Undefined SRS"))
     308             :     {
     309         401 :         m_oMapSrsIdToSrs[iSrsId] = nullptr;
     310         401 :         return nullptr;
     311             :     }
     312         557 :     const char *pszWkt = oResult->GetValue(1, 0);
     313         557 :     if (pszWkt == nullptr)
     314           0 :         return nullptr;
     315         557 :     const char *pszOrganization = oResult->GetValue(2, 0);
     316         557 :     const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
     317             :     const char *pszWkt2 =
     318         557 :         m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
     319         557 :     if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
     320          73 :         pszWkt = pszWkt2;
     321             :     const char *pszCoordinateEpoch =
     322         557 :         m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
     323             :     const double dfCoordinateEpoch =
     324         557 :         pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
     325             : 
     326         557 :     OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
     327         557 :     poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     328             :     // Try to import first from EPSG code, and then from WKT
     329         557 :     if (!(pszOrganization && pszOrganizationCoordsysID &&
     330         557 :           EQUAL(pszOrganization, "EPSG") &&
     331         538 :           (atoi(pszOrganizationCoordsysID) == iSrsId ||
     332           4 :            (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
     333         538 :           GDALGPKGImportFromEPSG(
     334        1114 :               poSpatialRef, atoi(pszOrganizationCoordsysID)) == OGRERR_NONE) &&
     335          19 :         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         557 :     poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
     346         557 :     poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
     347         557 :     m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
     348         557 :     poSpatialRef->Reference();
     349             :     return std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>(
     350         557 :         poSpatialRef);
     351             : }
     352             : 
     353         250 : const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
     354             : {
     355         250 :     const char *pszName = oSRS.GetName();
     356         250 :     if (pszName)
     357         250 :         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           6 : bool GDALGeoPackageDataset::ConvertGpkgSpatialRefSysToExtensionWkt2(
     365             :     bool bForceEpoch)
     366             : {
     367           6 :     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          12 :              "definition, description FROM gpkg_spatial_ref_sys LIMIT 100000");
     371           6 :     if (!oResultTable)
     372           0 :         return false;
     373             : 
     374             :     // Temporary remove foreign key checks
     375             :     const GPKGTemporaryForeignKeyCheckDisabler
     376           6 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
     377             : 
     378           6 :     bool bRet = SoftStartTransaction() == OGRERR_NONE;
     379             : 
     380           6 :     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           6 :                           "definition_12_063 TEXT NOT NULL");
     390           6 :         if (bAddEpoch)
     391           3 :             osSQL += ", epoch DOUBLE";
     392           6 :         osSQL += ")";
     393           6 :         bRet = SQLCommand(hDB, osSQL.c_str()) == OGRERR_NONE;
     394             :     }
     395             : 
     396           6 :     if (bRet)
     397             :     {
     398          28 :         for (int i = 0; bRet && i < oResultTable->RowCount(); i++)
     399             :         {
     400          22 :             const char *pszSrsName = oResultTable->GetValue(0, i);
     401          22 :             const char *pszSrsId = oResultTable->GetValue(1, i);
     402          22 :             const char *pszOrganization = oResultTable->GetValue(2, i);
     403             :             const char *pszOrganizationCoordsysID =
     404          22 :                 oResultTable->GetValue(3, i);
     405          22 :             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          22 :             const char *pszDescription = oResultTable->GetValue(5, i);
     415             :             char *pszSQL;
     416             : 
     417          44 :             OGRSpatialReference oSRS;
     418          22 :             if (pszOrganization && pszOrganizationCoordsysID &&
     419          22 :                 EQUAL(pszOrganization, "EPSG"))
     420             :             {
     421           8 :                 oSRS.importFromEPSG(atoi(pszOrganizationCoordsysID));
     422             :             }
     423          30 :             if (!oSRS.IsEmpty() && pszDefinition &&
     424           8 :                 !EQUAL(pszDefinition, "undefined"))
     425             :             {
     426           8 :                 oSRS.SetFromUserInput(pszDefinition);
     427             :             }
     428          22 :             char *pszWKT2 = nullptr;
     429          22 :             if (!oSRS.IsEmpty())
     430             :             {
     431           8 :                 const char *const apszOptionsWkt2[] = {"FORMAT=WKT2_2015",
     432             :                                                        nullptr};
     433           8 :                 oSRS.exportToWkt(&pszWKT2, apszOptionsWkt2);
     434           8 :                 if (pszWKT2 && pszWKT2[0] == '\0')
     435             :                 {
     436           0 :                     CPLFree(pszWKT2);
     437           0 :                     pszWKT2 = nullptr;
     438             :                 }
     439             :             }
     440          22 :             if (pszWKT2 == nullptr)
     441             :             {
     442          14 :                 pszWKT2 = CPLStrdup("undefined");
     443             :             }
     444             : 
     445          22 :             if (pszDescription)
     446             :             {
     447          19 :                 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          22 :             CPLFree(pszWKT2);
     468          22 :             bRet &= SQLCommand(hDB, pszSQL) == OGRERR_NONE;
     469          22 :             sqlite3_free(pszSQL);
     470             :         }
     471             :     }
     472             : 
     473           6 :     if (bRet)
     474             :     {
     475           6 :         bRet =
     476           6 :             SQLCommand(hDB, "DROP TABLE gpkg_spatial_ref_sys") == OGRERR_NONE;
     477             :     }
     478           6 :     if (bRet)
     479             :     {
     480           6 :         bRet = SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys_temp RENAME "
     481             :                                "TO gpkg_spatial_ref_sys") == OGRERR_NONE;
     482             :     }
     483           6 :     if (bRet)
     484             :     {
     485          12 :         bRet = OGRERR_NONE == CreateExtensionsTableIfNecessary() &&
     486           6 :                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           6 :     if (bRet && bAddEpoch)
     497             :     {
     498           3 :         bRet =
     499             :             OGRERR_NONE ==
     500           3 :                 SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
     501             :                                 "'gpkg_crs_wkt_1_1' "
     502           6 :                                 "WHERE extension_name = 'gpkg_crs_wkt'") &&
     503             :             OGRERR_NONE ==
     504           3 :                 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           6 :     if (bRet)
     515             :     {
     516           6 :         SoftCommitTransaction();
     517           6 :         m_bHasDefinition12_063 = true;
     518           6 :         if (bAddEpoch)
     519           3 :             m_bHasEpochColumn = true;
     520             :     }
     521             :     else
     522             :     {
     523           0 :         SoftRollbackTransaction();
     524             :     }
     525             : 
     526           6 :     return bRet;
     527             : }
     528             : 
     529         818 : int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
     530             : {
     531         818 :     const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
     532        1189 :     if (!poSRSIn || poSRSIn->IsEmpty() ||
     533         371 :         (pszName && EQUAL(pszName, "Undefined SRS")))
     534             :     {
     535         449 :         OGRErr err = OGRERR_NONE;
     536         449 :         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         449 :         if (err == OGRERR_NONE)
     542          54 :             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         395 :         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         394 :             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         395 :         if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
     586         395 :             return UNDEFINED_CRS_SRS_ID;
     587             : #undef UNDEFINED_CRS_SRS_ID
     588             : #undef XSTRINGIFY
     589             : #undef STRINGIFY
     590           0 :         return -1;
     591             :     }
     592             : 
     593         738 :     std::unique_ptr<OGRSpatialReference> poSRS(poSRSIn->Clone());
     594             : 
     595         369 :     if (poSRS->IsGeographic() || poSRS->IsLocal())
     596             :     {
     597             :         // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
     598         136 :         if (pszName != nullptr && strlen(pszName) > 0)
     599             :         {
     600         136 :             if (EQUAL(pszName, "Undefined geographic SRS"))
     601           2 :                 return 0;
     602             : 
     603         134 :             if (EQUAL(pszName, "Undefined Cartesian SRS"))
     604           1 :                 return -1;
     605             :         }
     606             :     }
     607             : 
     608         366 :     const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     609             : 
     610         366 :     if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0)
     611             :     {
     612             :         // Try to force identify an EPSG code.
     613          25 :         poSRS->AutoIdentifyEPSG();
     614             : 
     615          25 :         pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     616          25 :         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          25 :         poSRS->SetCoordinateEpoch(poSRSIn->GetCoordinateEpoch());
     629             :     }
     630             : 
     631             :     // Check whether the EPSG authority code is already mapped to a
     632             :     // SRS ID.
     633         366 :     char *pszSQL = nullptr;
     634         366 :     int nSRSId = DEFAULT_SRID;
     635         366 :     int nAuthorityCode = 0;
     636         366 :     OGRErr err = OGRERR_NONE;
     637         366 :     bool bCanUseAuthorityCode = false;
     638         366 :     const char *const apszIsSameOptions[] = {
     639             :         "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
     640             :         "IGNORE_COORDINATE_EPOCH=YES", nullptr};
     641         366 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
     642             :     {
     643         341 :         const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
     644         341 :         if (pszAuthorityCode)
     645             :         {
     646         341 :             if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
     647             :             {
     648         341 :                 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         707 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
     663         341 :         poSRSIn->GetCoordinateEpoch() == 0)
     664             :     {
     665             :         pszSQL =
     666         336 :             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         336 :         nSRSId = SQLGetInteger(hDB, pszSQL, &err);
     672         336 :         sqlite3_free(pszSQL);
     673             : 
     674             :         // Got a match? Return it!
     675         336 :         if (OGRERR_NONE == err)
     676             :         {
     677         112 :             auto poRefSRS = GetSpatialRef(nSRSId);
     678             :             bool bOK =
     679         112 :                 (poRefSRS == nullptr ||
     680         113 :                  poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) ||
     681           1 :                  !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
     682         112 :             if (bOK)
     683             :             {
     684         111 :                 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         255 :     CPLCharUniquePtr pszWKT1;
     702         255 :     CPLCharUniquePtr pszWKT2_2015;
     703         255 :     CPLCharUniquePtr pszWKT2_2019;
     704         255 :     const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
     705         255 :     const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
     706         255 :     const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
     707             : 
     708         510 :     std::string osEpochTest;
     709         255 :     if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
     710             :     {
     711             :         osEpochTest =
     712           3 :             CPLSPrintf(" AND epoch = %.17g", poSRSIn->GetCoordinateEpoch());
     713             :     }
     714             : 
     715         255 :     if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3))
     716             :     {
     717         248 :         char *pszTmp = nullptr;
     718         248 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
     719         248 :         pszWKT1.reset(pszTmp);
     720         248 :         if (pszWKT1 && pszWKT1.get()[0] == '\0')
     721             :         {
     722           0 :             pszWKT1.reset();
     723             :         }
     724             :     }
     725             :     {
     726         255 :         char *pszTmp = nullptr;
     727         255 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
     728         255 :         pszWKT2_2015.reset(pszTmp);
     729         255 :         if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
     730             :         {
     731           0 :             pszWKT2_2015.reset();
     732             :         }
     733             :     }
     734             :     {
     735         255 :         char *pszTmp = nullptr;
     736         255 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
     737         255 :         pszWKT2_2019.reset(pszTmp);
     738         255 :         if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
     739             :         {
     740           0 :             pszWKT2_2019.reset();
     741             :         }
     742             :     }
     743             : 
     744         255 :     if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
     745             :     {
     746           0 :         return DEFAULT_SRID;
     747             :     }
     748             : 
     749         255 :     if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
     750             :     {
     751             :         // Search if there is already an existing entry with this WKT
     752         252 :         if (m_bHasDefinition12_063 && (pszWKT2_2015 || pszWKT2_2019))
     753             :         {
     754          40 :             if (pszWKT1)
     755             :             {
     756         140 :                 pszSQL = sqlite3_mprintf(
     757             :                     "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     758             :                     "(definition = '%q' OR definition_12_063 IN ('%q','%q'))%s",
     759             :                     pszWKT1.get(),
     760          70 :                     pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
     761          70 :                     pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
     762             :                     osEpochTest.c_str());
     763             :             }
     764             :             else
     765             :             {
     766          20 :                 pszSQL = sqlite3_mprintf(
     767             :                     "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     768             :                     "definition_12_063 IN ('%q', '%q')%s",
     769          10 :                     pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
     770          10 :                     pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
     771             :                     osEpochTest.c_str());
     772             :             }
     773             :         }
     774         212 :         else if (pszWKT1)
     775             :         {
     776             :             pszSQL =
     777         210 :                 sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     778             :                                 "definition = '%q'%s",
     779             :                                 pszWKT1.get(), osEpochTest.c_str());
     780             :         }
     781             :         else
     782             :         {
     783           2 :             pszSQL = nullptr;
     784             :         }
     785         252 :         if (pszSQL)
     786             :         {
     787         250 :             nSRSId = SQLGetInteger(hDB, pszSQL, &err);
     788         250 :             sqlite3_free(pszSQL);
     789         250 :             if (OGRERR_NONE == err)
     790             :             {
     791           5 :                 return nSRSId;
     792             :             }
     793             :         }
     794             :     }
     795             : 
     796         477 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
     797         227 :         poSRSIn->GetCoordinateEpoch() == 0)
     798             :     {
     799         223 :         bool bTryToReuseSRSId = true;
     800         223 :         if (EQUAL(pszAuthorityName, "EPSG"))
     801             :         {
     802         444 :             OGRSpatialReference oSRS_EPSG;
     803         222 :             if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
     804             :                 OGRERR_NONE)
     805             :             {
     806         223 :                 if (!poSRS->IsSame(&oSRS_EPSG, apszIsSameOptions) &&
     807           1 :                     CPLTestBool(
     808             :                         CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")))
     809             :                 {
     810           1 :                     bTryToReuseSRSId = false;
     811           1 :                     CPLError(
     812             :                         CE_Warning, CPLE_AppDefined,
     813             :                         "Passed SRS uses %s:%d identification, but its "
     814             :                         "definition is not compatible with the "
     815             :                         "official definition of the object. "
     816             :                         "Registering it as a non-%s entry into the database.",
     817             :                         pszAuthorityName, nAuthorityCode, pszAuthorityName);
     818           1 :                     pszAuthorityName = nullptr;
     819           1 :                     nAuthorityCode = 0;
     820             :                 }
     821             :             }
     822             :         }
     823         223 :         if (bTryToReuseSRSId)
     824             :         {
     825             :             // No match, but maybe we can use the nAuthorityCode as the nSRSId?
     826         222 :             pszSQL = sqlite3_mprintf(
     827             :                 "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
     828             :                 "srs_id = %d",
     829             :                 nAuthorityCode);
     830             : 
     831             :             // Yep, we can!
     832         222 :             if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
     833         221 :                 bCanUseAuthorityCode = true;
     834         222 :             sqlite3_free(pszSQL);
     835             :         }
     836             :     }
     837             : 
     838         250 :     bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
     839         250 :     bool bForceEpoch = false;
     840         252 :     if (!m_bHasDefinition12_063 && pszWKT1 == nullptr &&
     841           2 :         (pszWKT2_2015 != nullptr || pszWKT2_2019 != nullptr))
     842             :     {
     843           2 :         bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
     844             :     }
     845             : 
     846             :     // Add epoch column if needed
     847         250 :     if (poSRSIn->GetCoordinateEpoch() > 0 && !m_bHasEpochColumn)
     848             :     {
     849           3 :         if (m_bHasDefinition12_063)
     850             :         {
     851           0 :             if (SoftStartTransaction() != OGRERR_NONE)
     852           0 :                 return DEFAULT_SRID;
     853           0 :             if (SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys "
     854           0 :                                 "ADD COLUMN epoch DOUBLE") != OGRERR_NONE ||
     855           0 :                 SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
     856             :                                 "'gpkg_crs_wkt_1_1' "
     857             :                                 "WHERE extension_name = 'gpkg_crs_wkt'") !=
     858           0 :                     OGRERR_NONE ||
     859           0 :                 SQLCommand(
     860             :                     hDB,
     861             :                     "INSERT INTO gpkg_extensions "
     862             :                     "(table_name, column_name, extension_name, definition, "
     863             :                     "scope) "
     864             :                     "VALUES "
     865             :                     "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
     866             :                     "'http://www.geopackage.org/spec/#extension_crs_wkt', "
     867             :                     "'read-write')") != OGRERR_NONE)
     868             :             {
     869           0 :                 SoftRollbackTransaction();
     870           0 :                 return DEFAULT_SRID;
     871             :             }
     872             : 
     873           0 :             if (SoftCommitTransaction() != OGRERR_NONE)
     874           0 :                 return DEFAULT_SRID;
     875             : 
     876           0 :             m_bHasEpochColumn = true;
     877             :         }
     878             :         else
     879             :         {
     880           3 :             bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
     881           3 :             bForceEpoch = true;
     882             :         }
     883             :     }
     884             : 
     885         255 :     if (bConvertGpkgSpatialRefSysToExtensionWkt2 &&
     886           5 :         !ConvertGpkgSpatialRefSysToExtensionWkt2(bForceEpoch))
     887             :     {
     888           0 :         return DEFAULT_SRID;
     889             :     }
     890             : 
     891             :     // Reuse the authority code number as SRS_ID if we can
     892         250 :     if (bCanUseAuthorityCode)
     893             :     {
     894         221 :         nSRSId = nAuthorityCode;
     895             :     }
     896             :     // Otherwise, generate a new SRS_ID number (max + 1)
     897             :     else
     898             :     {
     899             :         // Get the current maximum srid in the srs table.
     900          29 :         const int nMaxSRSId = SQLGetInteger(
     901             :             hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
     902          29 :         nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
     903             :     }
     904             : 
     905         500 :     std::string osEpochColumn;
     906         250 :     std::string osEpochVal;
     907         250 :     if (poSRSIn->GetCoordinateEpoch() > 0)
     908             :     {
     909           5 :         osEpochColumn = ", epoch";
     910           5 :         osEpochVal = CPLSPrintf(", %.17g", poSRSIn->GetCoordinateEpoch());
     911             :     }
     912             : 
     913             :     // Add new SRS row to gpkg_spatial_ref_sys.
     914         250 :     if (m_bHasDefinition12_063)
     915             :     {
     916             :         // Force WKT2_2019 when we have a dynamic CRS and coordinate epoch
     917          42 :         const char *pszWKT2 = poSRSIn->IsDynamic() &&
     918           8 :                                       poSRSIn->GetCoordinateEpoch() > 0 &&
     919           1 :                                       pszWKT2_2019
     920           1 :                                   ? pszWKT2_2019.get()
     921          41 :                               : pszWKT2_2015 ? pszWKT2_2015.get()
     922          89 :                                              : pszWKT2_2019.get();
     923             : 
     924          42 :         if (pszAuthorityName != nullptr && nAuthorityCode > 0)
     925             :         {
     926          93 :             pszSQL = sqlite3_mprintf(
     927             :                 "INSERT INTO gpkg_spatial_ref_sys "
     928             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     929             :                 "definition, definition_12_063%s) VALUES "
     930             :                 "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
     931          31 :                 osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId,
     932             :                 pszAuthorityName, nAuthorityCode,
     933          59 :                 pszWKT1 ? pszWKT1.get() : "undefined",
     934             :                 pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
     935             :         }
     936             :         else
     937             :         {
     938          33 :             pszSQL = sqlite3_mprintf(
     939             :                 "INSERT INTO gpkg_spatial_ref_sys "
     940             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     941             :                 "definition, definition_12_063%s) VALUES "
     942             :                 "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
     943          11 :                 osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId, "NONE",
     944          20 :                 nSRSId, pszWKT1 ? pszWKT1.get() : "undefined",
     945             :                 pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
     946             :         }
     947             :     }
     948             :     else
     949             :     {
     950         208 :         if (pszAuthorityName != nullptr && nAuthorityCode > 0)
     951             :         {
     952         390 :             pszSQL = sqlite3_mprintf(
     953             :                 "INSERT INTO gpkg_spatial_ref_sys "
     954             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     955             :                 "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
     956         195 :                 GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
     957         390 :                 pszWKT1 ? pszWKT1.get() : "undefined");
     958             :         }
     959             :         else
     960             :         {
     961          26 :             pszSQL = sqlite3_mprintf(
     962             :                 "INSERT INTO gpkg_spatial_ref_sys "
     963             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     964             :                 "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
     965          13 :                 GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
     966          26 :                 pszWKT1 ? pszWKT1.get() : "undefined");
     967             :         }
     968             :     }
     969             : 
     970             :     // Add new row to gpkg_spatial_ref_sys.
     971         250 :     CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
     972             : 
     973             :     // Free everything that was allocated.
     974         250 :     sqlite3_free(pszSQL);
     975             : 
     976         250 :     return nSRSId;
     977             : }
     978             : 
     979             : /************************************************************************/
     980             : /*                        GDALGeoPackageDataset()                       */
     981             : /************************************************************************/
     982             : 
     983        2348 : GDALGeoPackageDataset::GDALGeoPackageDataset()
     984             :     : m_nApplicationId(GPKG_APPLICATION_ID), m_nUserVersion(GPKG_1_2_VERSION),
     985             :       m_papoLayers(nullptr), m_nLayers(0),
     986             : #ifdef ENABLE_GPKG_OGR_CONTENTS
     987             :       m_bHasGPKGOGRContents(false),
     988             : #endif
     989             :       m_bHasGPKGGeometryColumns(false), m_bHasDefinition12_063(false),
     990             :       m_bIdentifierAsCO(false), m_bDescriptionAsCO(false),
     991             :       m_bHasReadMetadataFromStorage(false), m_bMetadataDirty(false),
     992             :       m_bRecordInsertedInGPKGContent(false), m_bGeoTransformValid(false),
     993             :       m_nSRID(-1),  // Unknown Cartesian.
     994             :       m_dfTMSMinX(0.0), m_dfTMSMaxY(0.0), m_nOverviewCount(0),
     995             :       m_papoOverviewDS(nullptr), m_bZoomOther(false), m_bInFlushCache(false),
     996             :       m_osTilingScheme("CUSTOM"), m_bMapTableToExtensionsBuilt(false),
     997        2348 :       m_bMapTableToContentsBuilt(false)
     998             : {
     999        2348 :     m_adfGeoTransform[0] = 0.0;
    1000        2348 :     m_adfGeoTransform[1] = 1.0;
    1001        2348 :     m_adfGeoTransform[2] = 0.0;
    1002        2348 :     m_adfGeoTransform[3] = 0.0;
    1003        2348 :     m_adfGeoTransform[4] = 0.0;
    1004        2348 :     m_adfGeoTransform[5] = 1.0;
    1005        2348 : }
    1006             : 
    1007             : /************************************************************************/
    1008             : /*                       ~GDALGeoPackageDataset()                       */
    1009             : /************************************************************************/
    1010             : 
    1011        4696 : GDALGeoPackageDataset::~GDALGeoPackageDataset()
    1012             : {
    1013        2348 :     GDALGeoPackageDataset::Close();
    1014        4696 : }
    1015             : 
    1016             : /************************************************************************/
    1017             : /*                              Close()                                 */
    1018             : /************************************************************************/
    1019             : 
    1020        3944 : CPLErr GDALGeoPackageDataset::Close()
    1021             : {
    1022        3944 :     CPLErr eErr = CE_None;
    1023        3944 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
    1024             :     {
    1025        1364 :         if (eAccess == GA_Update && m_poParentDS == nullptr &&
    1026        3712 :             !m_osRasterTable.empty() && !m_bGeoTransformValid)
    1027             :         {
    1028           3 :             CPLError(CE_Failure, CPLE_AppDefined,
    1029             :                      "Raster table %s not correctly initialized due to missing "
    1030             :                      "call to SetGeoTransform()",
    1031             :                      m_osRasterTable.c_str());
    1032             :         }
    1033             : 
    1034        2348 :         if (GDALGeoPackageDataset::FlushCache(true) != CE_None)
    1035           7 :             eErr = CE_Failure;
    1036             : 
    1037             :         // Destroy bands now since we don't want
    1038             :         // GDALGPKGMBTilesLikeRasterBand::FlushCache() to run after dataset
    1039             :         // destruction
    1040        4151 :         for (int i = 0; i < nBands; i++)
    1041        1803 :             delete papoBands[i];
    1042        2348 :         nBands = 0;
    1043        2348 :         CPLFree(papoBands);
    1044        2348 :         papoBands = nullptr;
    1045             : 
    1046             :         // Destroy overviews before cleaning m_hTempDB as they could still
    1047             :         // need it
    1048        2673 :         for (int i = 0; i < m_nOverviewCount; i++)
    1049         325 :             delete m_papoOverviewDS[i];
    1050             : 
    1051        2348 :         if (m_poParentDS)
    1052             :         {
    1053         325 :             hDB = nullptr;
    1054             :         }
    1055             : 
    1056        6079 :         for (int i = 0; i < m_nLayers; i++)
    1057        3731 :             delete m_papoLayers[i];
    1058             : 
    1059        2348 :         CPLFree(m_papoLayers);
    1060        2348 :         CPLFree(m_papoOverviewDS);
    1061             : 
    1062             :         std::map<int, OGRSpatialReference *>::iterator oIter =
    1063        2348 :             m_oMapSrsIdToSrs.begin();
    1064        3428 :         for (; oIter != m_oMapSrsIdToSrs.end(); ++oIter)
    1065             :         {
    1066        1080 :             OGRSpatialReference *poSRS = oIter->second;
    1067        1080 :             if (poSRS)
    1068         677 :                 poSRS->Release();
    1069             :         }
    1070             : 
    1071        2348 :         if (!CloseDB())
    1072           0 :             eErr = CE_Failure;
    1073             : 
    1074        2348 :         if (OGRSQLiteBaseDataSource::Close() != CE_None)
    1075           0 :             eErr = CE_Failure;
    1076             :     }
    1077        3944 :     return eErr;
    1078             : }
    1079             : 
    1080             : /************************************************************************/
    1081             : /*                         ICanIWriteBlock()                            */
    1082             : /************************************************************************/
    1083             : 
    1084        5694 : bool GDALGeoPackageDataset::ICanIWriteBlock()
    1085             : {
    1086        5694 :     if (!GetUpdate())
    1087             :     {
    1088           0 :         CPLError(
    1089             :             CE_Failure, CPLE_NotSupported,
    1090             :             "IWriteBlock() not supported on dataset opened in read-only mode");
    1091           0 :         return false;
    1092             :     }
    1093             : 
    1094        5694 :     if (m_pabyCachedTiles == nullptr)
    1095             :     {
    1096           0 :         return false;
    1097             :     }
    1098             : 
    1099        5694 :     if (!m_bGeoTransformValid || m_nSRID == UNKNOWN_SRID)
    1100             :     {
    1101           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1102             :                  "IWriteBlock() not supported if georeferencing not set");
    1103           0 :         return false;
    1104             :     }
    1105        5694 :     return true;
    1106             : }
    1107             : 
    1108             : /************************************************************************/
    1109             : /*                            IRasterIO()                               */
    1110             : /************************************************************************/
    1111             : 
    1112         130 : CPLErr GDALGeoPackageDataset::IRasterIO(
    1113             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
    1114             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
    1115             :     int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
    1116             :     GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
    1117             : 
    1118             : {
    1119         130 :     CPLErr eErr = OGRSQLiteBaseDataSource::IRasterIO(
    1120             :         eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    1121             :         eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
    1122             :         psExtraArg);
    1123             : 
    1124             :     // If writing all bands, in non-shifted mode, flush all entirely written
    1125             :     // tiles This can avoid "stressing" the block cache with too many dirty
    1126             :     // blocks. Note: this logic would be useless with a per-dataset block cache.
    1127         130 :     if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
    1128         121 :         nYSize == nBufYSize && nBandCount == nBands &&
    1129         118 :         m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
    1130             :     {
    1131             :         auto poBand =
    1132         114 :             cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
    1133             :         int nBlockXSize, nBlockYSize;
    1134         114 :         poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
    1135         114 :         const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
    1136         114 :         const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
    1137         114 :         const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
    1138         114 :         const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
    1139         268 :         for (int nBlockY = nBlockXStart; nBlockY < nBlockYEnd; nBlockY++)
    1140             :         {
    1141        4371 :             for (int nBlockX = nBlockYStart; nBlockX < nBlockXEnd; nBlockX++)
    1142             :             {
    1143             :                 GDALRasterBlock *poBlock =
    1144        4217 :                     poBand->AccessibleTryGetLockedBlockRef(nBlockX, nBlockY);
    1145        4217 :                 if (poBlock)
    1146             :                 {
    1147             :                     // GetDirty() should be true in most situation (otherwise
    1148             :                     // it means the block cache is under extreme pressure!)
    1149        4215 :                     if (poBlock->GetDirty())
    1150             :                     {
    1151             :                         // IWriteBlock() on one band will check the dirty state
    1152             :                         // of the corresponding blocks in other bands, to decide
    1153             :                         // if it can call WriteTile(), so we have only to do
    1154             :                         // that on one of the bands
    1155        4215 :                         if (poBlock->Write() != CE_None)
    1156         250 :                             eErr = CE_Failure;
    1157             :                     }
    1158        4215 :                     poBlock->DropLock();
    1159             :                 }
    1160             :             }
    1161             :         }
    1162             :     }
    1163             : 
    1164         130 :     return eErr;
    1165             : }
    1166             : 
    1167             : /************************************************************************/
    1168             : /*                          GetOGRTableLimit()                          */
    1169             : /************************************************************************/
    1170             : 
    1171        3808 : static int GetOGRTableLimit()
    1172             : {
    1173        3808 :     return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
    1174             : }
    1175             : 
    1176             : /************************************************************************/
    1177             : /*                      GetNameTypeMapFromSQliteMaster()                */
    1178             : /************************************************************************/
    1179             : 
    1180             : const std::map<CPLString, CPLString> &
    1181        1174 : GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
    1182             : {
    1183        1174 :     if (!m_oMapNameToType.empty())
    1184         323 :         return m_oMapNameToType;
    1185             : 
    1186             :     CPLString osSQL(
    1187             :         "SELECT name, type FROM sqlite_master WHERE "
    1188             :         "type IN ('view', 'table') OR "
    1189        1702 :         "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
    1190         851 :     const int nTableLimit = GetOGRTableLimit();
    1191         851 :     if (nTableLimit > 0)
    1192             :     {
    1193         851 :         osSQL += " LIMIT ";
    1194         851 :         osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
    1195             :     }
    1196             : 
    1197         851 :     auto oResult = SQLQuery(hDB, osSQL);
    1198         851 :     if (oResult)
    1199             :     {
    1200       14241 :         for (int i = 0; i < oResult->RowCount(); i++)
    1201             :         {
    1202       13390 :             const char *pszName = oResult->GetValue(0, i);
    1203       13390 :             const char *pszType = oResult->GetValue(1, i);
    1204       13390 :             m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
    1205             :         }
    1206             :     }
    1207             : 
    1208         851 :     return m_oMapNameToType;
    1209             : }
    1210             : 
    1211             : /************************************************************************/
    1212             : /*                    RemoveTableFromSQLiteMasterCache()                */
    1213             : /************************************************************************/
    1214             : 
    1215          54 : void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
    1216             :     const char *pszTableName)
    1217             : {
    1218          54 :     m_oMapNameToType.erase(CPLString(pszTableName).toupper());
    1219          54 : }
    1220             : 
    1221             : /************************************************************************/
    1222             : /*                  GetUnknownExtensionsTableSpecific()                 */
    1223             : /************************************************************************/
    1224             : 
    1225             : const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
    1226         813 : GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
    1227             : {
    1228         813 :     if (m_bMapTableToExtensionsBuilt)
    1229          81 :         return m_oMapTableToExtensions;
    1230         732 :     m_bMapTableToExtensionsBuilt = true;
    1231             : 
    1232         732 :     if (!HasExtensionsTable())
    1233          38 :         return m_oMapTableToExtensions;
    1234             : 
    1235             :     CPLString osSQL(
    1236             :         "SELECT table_name, extension_name, definition, scope "
    1237             :         "FROM gpkg_extensions WHERE "
    1238             :         "table_name IS NOT NULL "
    1239             :         "AND extension_name IS NOT NULL "
    1240             :         "AND definition IS NOT NULL "
    1241             :         "AND scope IS NOT NULL "
    1242             :         "AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
    1243             :         "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', "
    1244             :         "'gpkg_geom_MULTICURVE', "
    1245             :         "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
    1246             :         "'gpkg_geom_POLYHEDRALSURFACE', 'gpkg_geom_TIN', 'gpkg_geom_TRIANGLE', "
    1247             :         "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', "
    1248             :         "'gpkg_srs_id_trigger', "
    1249             :         "'gpkg_crs_wkt', 'gpkg_crs_wkt_1_1', 'gpkg_schema', "
    1250             :         "'gpkg_related_tables', 'related_tables'"
    1251             : #ifdef HAVE_SPATIALITE
    1252             :         ", 'gdal_spatialite_computed_geom_column'"
    1253             : #endif
    1254        1388 :         ")");
    1255         694 :     const int nTableLimit = GetOGRTableLimit();
    1256         694 :     if (nTableLimit > 0)
    1257             :     {
    1258         694 :         osSQL += " LIMIT ";
    1259         694 :         osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
    1260             :     }
    1261             : 
    1262         694 :     auto oResult = SQLQuery(hDB, osSQL);
    1263         694 :     if (oResult)
    1264             :     {
    1265        1315 :         for (int i = 0; i < oResult->RowCount(); i++)
    1266             :         {
    1267         621 :             const char *pszTableName = oResult->GetValue(0, i);
    1268         621 :             const char *pszExtensionName = oResult->GetValue(1, i);
    1269         621 :             const char *pszDefinition = oResult->GetValue(2, i);
    1270         621 :             const char *pszScope = oResult->GetValue(3, i);
    1271         621 :             if (pszTableName && pszExtensionName && pszDefinition && pszScope)
    1272             :             {
    1273         621 :                 GPKGExtensionDesc oDesc;
    1274         621 :                 oDesc.osExtensionName = pszExtensionName;
    1275         621 :                 oDesc.osDefinition = pszDefinition;
    1276         621 :                 oDesc.osScope = pszScope;
    1277        1242 :                 m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
    1278         621 :                     .push_back(oDesc);
    1279             :             }
    1280             :         }
    1281             :     }
    1282             : 
    1283         694 :     return m_oMapTableToExtensions;
    1284             : }
    1285             : 
    1286             : /************************************************************************/
    1287             : /*                           GetContents()                              */
    1288             : /************************************************************************/
    1289             : 
    1290             : const std::map<CPLString, GPKGContentsDesc> &
    1291         796 : GDALGeoPackageDataset::GetContents()
    1292             : {
    1293         796 :     if (m_bMapTableToContentsBuilt)
    1294          66 :         return m_oMapTableToContents;
    1295         730 :     m_bMapTableToContentsBuilt = true;
    1296             : 
    1297             :     CPLString osSQL("SELECT table_name, data_type, identifier, "
    1298             :                     "description, min_x, min_y, max_x, max_y "
    1299        1460 :                     "FROM gpkg_contents");
    1300         730 :     const int nTableLimit = GetOGRTableLimit();
    1301         730 :     if (nTableLimit > 0)
    1302             :     {
    1303         730 :         osSQL += " LIMIT ";
    1304         730 :         osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1305             :     }
    1306             : 
    1307         730 :     auto oResult = SQLQuery(hDB, osSQL);
    1308         730 :     if (oResult)
    1309             :     {
    1310        1570 :         for (int i = 0; i < oResult->RowCount(); i++)
    1311             :         {
    1312         840 :             const char *pszTableName = oResult->GetValue(0, i);
    1313         840 :             if (pszTableName == nullptr)
    1314           0 :                 continue;
    1315         840 :             const char *pszDataType = oResult->GetValue(1, i);
    1316         840 :             const char *pszIdentifier = oResult->GetValue(2, i);
    1317         840 :             const char *pszDescription = oResult->GetValue(3, i);
    1318         840 :             const char *pszMinX = oResult->GetValue(4, i);
    1319         840 :             const char *pszMinY = oResult->GetValue(5, i);
    1320         840 :             const char *pszMaxX = oResult->GetValue(6, i);
    1321         840 :             const char *pszMaxY = oResult->GetValue(7, i);
    1322         840 :             GPKGContentsDesc oDesc;
    1323         840 :             if (pszDataType)
    1324         840 :                 oDesc.osDataType = pszDataType;
    1325         840 :             if (pszIdentifier)
    1326         840 :                 oDesc.osIdentifier = pszIdentifier;
    1327         840 :             if (pszDescription)
    1328         839 :                 oDesc.osDescription = pszDescription;
    1329         840 :             if (pszMinX)
    1330         571 :                 oDesc.osMinX = pszMinX;
    1331         840 :             if (pszMinY)
    1332         571 :                 oDesc.osMinY = pszMinY;
    1333         840 :             if (pszMaxX)
    1334         571 :                 oDesc.osMaxX = pszMaxX;
    1335         840 :             if (pszMaxY)
    1336         571 :                 oDesc.osMaxY = pszMaxY;
    1337        1680 :             m_oMapTableToContents[CPLString(pszTableName).toupper()] =
    1338        1680 :                 std::move(oDesc);
    1339             :         }
    1340             :     }
    1341             : 
    1342         730 :     return m_oMapTableToContents;
    1343             : }
    1344             : 
    1345             : /************************************************************************/
    1346             : /*                                Open()                                */
    1347             : /************************************************************************/
    1348             : 
    1349        1165 : int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
    1350             :                                 const std::string &osFilenameInZip)
    1351             : {
    1352        1165 :     m_osFilenameInZip = osFilenameInZip;
    1353        1165 :     CPLAssert(m_nLayers == 0);
    1354        1165 :     CPLAssert(hDB == nullptr);
    1355             : 
    1356        1165 :     SetDescription(poOpenInfo->pszFilename);
    1357        2330 :     CPLString osFilename(poOpenInfo->pszFilename);
    1358        2330 :     CPLString osSubdatasetTableName;
    1359             :     GByte abyHeaderLetMeHerePlease[100];
    1360        1165 :     const GByte *pabyHeader = poOpenInfo->pabyHeader;
    1361        1165 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
    1362             :     {
    1363         240 :         char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
    1364             :                                                 CSLT_HONOURSTRINGS);
    1365         240 :         int nCount = CSLCount(papszTokens);
    1366         240 :         if (nCount < 2)
    1367             :         {
    1368           0 :             CSLDestroy(papszTokens);
    1369           0 :             return FALSE;
    1370             :         }
    1371             : 
    1372         240 :         if (nCount <= 3)
    1373             :         {
    1374         238 :             osFilename = papszTokens[1];
    1375             :         }
    1376             :         /* GPKG:C:\BLA.GPKG:foo */
    1377           2 :         else if (nCount == 4 && strlen(papszTokens[1]) == 1 &&
    1378           2 :                  (papszTokens[2][0] == '/' || papszTokens[2][0] == '\\'))
    1379             :         {
    1380           2 :             osFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
    1381             :         }
    1382             :         // GPKG:/vsicurl/http[s]://[user:passwd@]example.com[:8080]/foo.gpkg:bar
    1383           0 :         else if (/*nCount >= 4 && */
    1384           0 :                  (EQUAL(papszTokens[1], "/vsicurl/http") ||
    1385           0 :                   EQUAL(papszTokens[1], "/vsicurl/https")))
    1386             :         {
    1387           0 :             osFilename = CPLString(papszTokens[1]);
    1388           0 :             for (int i = 2; i < nCount - 1; i++)
    1389             :             {
    1390           0 :                 osFilename += ':';
    1391           0 :                 osFilename += papszTokens[i];
    1392             :             }
    1393             :         }
    1394         240 :         if (nCount >= 3)
    1395          12 :             osSubdatasetTableName = papszTokens[nCount - 1];
    1396             : 
    1397         240 :         CSLDestroy(papszTokens);
    1398         240 :         VSILFILE *fp = VSIFOpenL(osFilename, "rb");
    1399         240 :         if (fp != nullptr)
    1400             :         {
    1401         240 :             VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
    1402         240 :             VSIFCloseL(fp);
    1403             :         }
    1404         240 :         pabyHeader = abyHeaderLetMeHerePlease;
    1405             :     }
    1406         925 :     else if (poOpenInfo->pabyHeader &&
    1407         925 :              STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1408             :                          "SQLite format 3"))
    1409             :     {
    1410         919 :         m_bCallUndeclareFileNotToOpen = true;
    1411         919 :         GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
    1412             :                                          poOpenInfo->nHeaderBytes);
    1413             :     }
    1414             : 
    1415        1165 :     eAccess = poOpenInfo->eAccess;
    1416        1165 :     if (!m_osFilenameInZip.empty())
    1417             :     {
    1418           1 :         m_pszFilename = CPLStrdup(CPLSPrintf(
    1419             :             "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
    1420             :     }
    1421             :     else
    1422             :     {
    1423        1164 :         m_pszFilename = CPLStrdup(osFilename);
    1424             :     }
    1425             : 
    1426        1165 :     if (poOpenInfo->papszOpenOptions)
    1427             :     {
    1428         100 :         CSLDestroy(papszOpenOptions);
    1429         100 :         papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    1430             :     }
    1431             : 
    1432             : #ifdef ENABLE_SQL_GPKG_FORMAT
    1433        1165 :     if (poOpenInfo->pabyHeader &&
    1434         925 :         STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1435           5 :                     "-- SQL GPKG") &&
    1436           5 :         poOpenInfo->fpL != nullptr)
    1437             :     {
    1438           5 :         if (sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr) !=
    1439             :             SQLITE_OK)
    1440             :         {
    1441           0 :             return FALSE;
    1442             :         }
    1443             : 
    1444           5 :         InstallSQLFunctions();
    1445             : 
    1446             :         // Ingest the lines of the dump
    1447           5 :         VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
    1448             :         const char *pszLine;
    1449          76 :         while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
    1450             :         {
    1451          71 :             if (STARTS_WITH(pszLine, "--"))
    1452           5 :                 continue;
    1453             : 
    1454             :             // Reject a few words tat might have security implications
    1455             :             // Basically we just want to allow CREATE TABLE and INSERT INTO
    1456          66 :             if (CPLString(pszLine).ifind("ATTACH") != std::string::npos ||
    1457         132 :                 CPLString(pszLine).ifind("DETACH") != std::string::npos ||
    1458         132 :                 CPLString(pszLine).ifind("PRAGMA") != std::string::npos ||
    1459         132 :                 CPLString(pszLine).ifind("SELECT") != std::string::npos ||
    1460         128 :                 CPLString(pszLine).ifind("UPDATE") != std::string::npos ||
    1461         128 :                 CPLString(pszLine).ifind("REPLACE") != std::string::npos ||
    1462         128 :                 CPLString(pszLine).ifind("DELETE") != std::string::npos ||
    1463         128 :                 CPLString(pszLine).ifind("DROP") != std::string::npos ||
    1464         260 :                 CPLString(pszLine).ifind("ALTER") != std::string::npos ||
    1465         128 :                 CPLString(pszLine).ifind("VIRTUAL") != std::string::npos)
    1466             :             {
    1467           8 :                 bool bOK = false;
    1468             :                 // Accept creation of spatial index
    1469           8 :                 if (STARTS_WITH_CI(pszLine, "CREATE VIRTUAL TABLE "))
    1470             :                 {
    1471           4 :                     const char *pszStr =
    1472             :                         pszLine + strlen("CREATE VIRTUAL TABLE ");
    1473           4 :                     if (*pszStr == '"')
    1474           0 :                         pszStr++;
    1475          52 :                     while ((*pszStr >= 'a' && *pszStr <= 'z') ||
    1476          64 :                            (*pszStr >= 'A' && *pszStr <= 'Z') || *pszStr == '_')
    1477             :                     {
    1478          60 :                         pszStr++;
    1479             :                     }
    1480           4 :                     if (*pszStr == '"')
    1481           0 :                         pszStr++;
    1482           4 :                     if (EQUAL(pszStr,
    1483             :                               " USING rtree(id, minx, maxx, miny, maxy);"))
    1484             :                     {
    1485           4 :                         bOK = true;
    1486             :                     }
    1487             :                 }
    1488             :                 // Accept INSERT INTO rtree_poly_geom SELECT fid, ST_MinX(geom),
    1489             :                 // ST_MaxX(geom), ST_MinY(geom), ST_MaxY(geom) FROM poly;
    1490           8 :                 else if (STARTS_WITH_CI(pszLine, "INSERT INTO rtree_") &&
    1491           8 :                          CPLString(pszLine).ifind("SELECT") !=
    1492             :                              std::string::npos)
    1493             :                 {
    1494             :                     char **papszTokens =
    1495           4 :                         CSLTokenizeString2(pszLine, " (),,", 0);
    1496           4 :                     if (CSLCount(papszTokens) == 15 &&
    1497           4 :                         EQUAL(papszTokens[3], "SELECT") &&
    1498           4 :                         EQUAL(papszTokens[5], "ST_MinX") &&
    1499           4 :                         EQUAL(papszTokens[7], "ST_MaxX") &&
    1500           4 :                         EQUAL(papszTokens[9], "ST_MinY") &&
    1501          12 :                         EQUAL(papszTokens[11], "ST_MaxY") &&
    1502           4 :                         EQUAL(papszTokens[13], "FROM"))
    1503             :                     {
    1504           4 :                         bOK = TRUE;
    1505             :                     }
    1506           4 :                     CSLDestroy(papszTokens);
    1507             :                 }
    1508             : 
    1509           8 :                 if (!bOK)
    1510             :                 {
    1511           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
    1512             :                              "Rejected statement: %s", pszLine);
    1513           0 :                     return FALSE;
    1514             :                 }
    1515             :             }
    1516          66 :             char *pszErrMsg = nullptr;
    1517          66 :             if (sqlite3_exec(hDB, pszLine, nullptr, nullptr, &pszErrMsg) !=
    1518             :                 SQLITE_OK)
    1519             :             {
    1520           0 :                 if (pszErrMsg)
    1521           0 :                     CPLDebug("SQLITE", "Error %s", pszErrMsg);
    1522             :             }
    1523          66 :             sqlite3_free(pszErrMsg);
    1524           5 :         }
    1525             :     }
    1526             : 
    1527        1160 :     else if (pabyHeader != nullptr)
    1528             : #endif
    1529             :     {
    1530        1160 :         if (poOpenInfo->fpL)
    1531             :         {
    1532             :             // See above comment about -wal locking for the importance of
    1533             :             // closing that file, prior to calling sqlite3_open()
    1534         820 :             VSIFCloseL(poOpenInfo->fpL);
    1535         820 :             poOpenInfo->fpL = nullptr;
    1536             :         }
    1537             : 
    1538             :         /* See if we can open the SQLite database */
    1539        1160 :         if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
    1540             :                                         : SQLITE_OPEN_READONLY))
    1541           2 :             return FALSE;
    1542             : 
    1543        1158 :         memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
    1544        1158 :         m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
    1545        1158 :         memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
    1546        1158 :         m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
    1547        1158 :         if (m_nApplicationId == GP10_APPLICATION_ID)
    1548             :         {
    1549           7 :             CPLDebug("GPKG", "GeoPackage v1.0");
    1550             :         }
    1551        1151 :         else if (m_nApplicationId == GP11_APPLICATION_ID)
    1552             :         {
    1553           2 :             CPLDebug("GPKG", "GeoPackage v1.1");
    1554             :         }
    1555        1149 :         else if (m_nApplicationId == GPKG_APPLICATION_ID &&
    1556        1146 :                  m_nUserVersion >= GPKG_1_2_VERSION)
    1557             :         {
    1558        1144 :             CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
    1559        1144 :                      (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
    1560             :         }
    1561             :     }
    1562             : 
    1563             :     /* Requirement 6: The SQLite PRAGMA integrity_check SQL command SHALL return
    1564             :      * “ok” */
    1565             :     /* http://opengis.github.io/geopackage/#_file_integrity */
    1566             :     /* Disable integrity check by default, since it is expensive on big files */
    1567        1163 :     if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_INTEGRITY_CHECK", "NO")) &&
    1568           0 :         OGRERR_NONE != PragmaCheck("integrity_check", "ok", 1))
    1569             :     {
    1570           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1571             :                  "pragma integrity_check on '%s' failed", m_pszFilename);
    1572           0 :         return FALSE;
    1573             :     }
    1574             : 
    1575             :     /* Requirement 7: The SQLite PRAGMA foreign_key_check() SQL with no */
    1576             :     /* parameter value SHALL return an empty result set */
    1577             :     /* http://opengis.github.io/geopackage/#_file_integrity */
    1578             :     /* Disable the check by default, since it is to corrupt databases, and */
    1579             :     /* that causes issues to downstream software that can't open them. */
    1580        1163 :     if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_FOREIGN_KEY_CHECK", "NO")) &&
    1581           0 :         OGRERR_NONE != PragmaCheck("foreign_key_check", "", 0))
    1582             :     {
    1583           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1584             :                  "pragma foreign_key_check on '%s' failed.", m_pszFilename);
    1585           0 :         return FALSE;
    1586             :     }
    1587             : 
    1588             :     /* Check for requirement metadata tables */
    1589             :     /* Requirement 10: gpkg_spatial_ref_sys must exist */
    1590             :     /* Requirement 13: gpkg_contents must exist */
    1591        1163 :     if (SQLGetInteger(hDB,
    1592             :                       "SELECT COUNT(*) FROM sqlite_master WHERE "
    1593             :                       "name IN ('gpkg_spatial_ref_sys', 'gpkg_contents') AND "
    1594             :                       "type IN ('table', 'view')",
    1595        1163 :                       nullptr) != 2)
    1596             :     {
    1597           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1598             :                  "At least one of the required GeoPackage tables, "
    1599             :                  "gpkg_spatial_ref_sys or gpkg_contents, is missing");
    1600           0 :         return FALSE;
    1601             :     }
    1602             : 
    1603        1163 :     DetectSpatialRefSysColumns();
    1604             : 
    1605             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    1606        1163 :     if (SQLGetInteger(hDB,
    1607             :                       "SELECT 1 FROM sqlite_master WHERE "
    1608             :                       "name = 'gpkg_ogr_contents' AND type = 'table'",
    1609        1163 :                       nullptr) == 1)
    1610             :     {
    1611        1155 :         m_bHasGPKGOGRContents = true;
    1612             :     }
    1613             : #endif
    1614             : 
    1615        1163 :     CheckUnknownExtensions();
    1616             : 
    1617        1163 :     int bRet = FALSE;
    1618        1163 :     bool bHasGPKGExtRelations = false;
    1619        1163 :     if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
    1620             :     {
    1621         978 :         m_bHasGPKGGeometryColumns =
    1622         978 :             SQLGetInteger(hDB,
    1623             :                           "SELECT 1 FROM sqlite_master WHERE "
    1624             :                           "name = 'gpkg_geometry_columns' AND "
    1625             :                           "type IN ('table', 'view')",
    1626         978 :                           nullptr) == 1;
    1627         978 :         bHasGPKGExtRelations = HasGpkgextRelationsTable();
    1628             :     }
    1629        1163 :     if (m_bHasGPKGGeometryColumns)
    1630             :     {
    1631             :         /* Load layer definitions for all tables in gpkg_contents &
    1632             :          * gpkg_geometry_columns */
    1633             :         /* and non-spatial tables as well */
    1634             :         std::string osSQL =
    1635             :             "SELECT c.table_name, c.identifier, 1 as is_spatial, "
    1636             :             "g.column_name, g.geometry_type_name, g.z, g.m, c.min_x, c.min_y, "
    1637             :             "c.max_x, c.max_y, 1 AS is_in_gpkg_contents, "
    1638             :             "(SELECT type FROM sqlite_master WHERE lower(name) = "
    1639             :             "lower(c.table_name) AND type IN ('table', 'view')) AS object_type "
    1640             :             "  FROM gpkg_geometry_columns g "
    1641             :             "  JOIN gpkg_contents c ON (g.table_name = c.table_name)"
    1642             :             "  WHERE "
    1643             :             "  c.table_name <> 'ogr_empty_table' AND"
    1644             :             "  c.data_type = 'features' "
    1645             :             // aspatial: Was the only method available in OGR 2.0 and 2.1
    1646             :             // attributes: GPKG 1.2 or later
    1647             :             "UNION ALL "
    1648             :             "SELECT table_name, identifier, 0 as is_spatial, NULL, NULL, 0, 0, "
    1649             :             "0 AS xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 1 AS "
    1650             :             "is_in_gpkg_contents, "
    1651             :             "(SELECT type FROM sqlite_master WHERE lower(name) = "
    1652             :             "lower(table_name) AND type IN ('table', 'view')) AS object_type "
    1653             :             "  FROM gpkg_contents"
    1654         977 :             "  WHERE data_type IN ('aspatial', 'attributes') ";
    1655             : 
    1656        1954 :         const char *pszListAllTables = CSLFetchNameValueDef(
    1657         977 :             poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
    1658         977 :         bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
    1659         977 :         if (!bHasASpatialOrAttributes)
    1660             :         {
    1661             :             auto oResultTable =
    1662             :                 SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
    1663         976 :                               "data_type = 'attributes' LIMIT 1");
    1664         976 :             bHasASpatialOrAttributes =
    1665         976 :                 (oResultTable && oResultTable->RowCount() == 1);
    1666             :         }
    1667         977 :         if (bHasGPKGExtRelations)
    1668             :         {
    1669             :             osSQL += "UNION ALL "
    1670             :                      "SELECT mapping_table_name, mapping_table_name, 0 as "
    1671             :                      "is_spatial, NULL, NULL, 0, 0, 0 AS "
    1672             :                      "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
    1673             :                      "is_in_gpkg_contents, 'table' AS object_type "
    1674             :                      "FROM gpkgext_relations WHERE "
    1675             :                      "lower(mapping_table_name) NOT IN (SELECT "
    1676             :                      "lower(table_name) FROM gpkg_contents) AND "
    1677             :                      "EXISTS (SELECT 1 FROM sqlite_master WHERE "
    1678             :                      "type IN ('table', 'view') AND "
    1679          18 :                      "lower(name) = lower(mapping_table_name))";
    1680             :         }
    1681         977 :         if (EQUAL(pszListAllTables, "YES") ||
    1682         976 :             (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
    1683             :         {
    1684             :             // vgpkg_ is Spatialite virtual table
    1685             :             osSQL +=
    1686             :                 "UNION ALL "
    1687             :                 "SELECT name, name, 0 as is_spatial, NULL, NULL, 0, 0, 0 AS "
    1688             :                 "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
    1689             :                 "is_in_gpkg_contents, type AS object_type "
    1690             :                 "FROM sqlite_master WHERE type IN ('table', 'view') "
    1691             :                 "AND name NOT LIKE 'gpkg_%' "
    1692             :                 "AND name NOT LIKE 'vgpkg_%' "
    1693             :                 "AND name NOT LIKE 'rtree_%' AND name NOT LIKE 'sqlite_%' "
    1694             :                 // Avoid reading those views from simple_sewer_features.gpkg
    1695             :                 "AND name NOT IN ('st_spatial_ref_sys', 'spatial_ref_sys', "
    1696             :                 "'st_geometry_columns', 'geometry_columns') "
    1697             :                 "AND lower(name) NOT IN (SELECT lower(table_name) FROM "
    1698         918 :                 "gpkg_contents)";
    1699         918 :             if (bHasGPKGExtRelations)
    1700             :             {
    1701             :                 osSQL += " AND lower(name) NOT IN (SELECT "
    1702             :                          "lower(mapping_table_name) FROM "
    1703          13 :                          "gpkgext_relations)";
    1704             :             }
    1705             :         }
    1706         977 :         const int nTableLimit = GetOGRTableLimit();
    1707         977 :         if (nTableLimit > 0)
    1708             :         {
    1709         977 :             osSQL += " LIMIT ";
    1710         977 :             osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1711             :         }
    1712             : 
    1713         977 :         auto oResult = SQLQuery(hDB, osSQL.c_str());
    1714         977 :         if (!oResult)
    1715             :         {
    1716           0 :             return FALSE;
    1717             :         }
    1718             : 
    1719         977 :         if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
    1720             :         {
    1721           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1722             :                      "File has more than %d vector tables. "
    1723             :                      "Limiting to first %d (can be overridden with "
    1724             :                      "OGR_TABLE_LIMIT config option)",
    1725             :                      nTableLimit, nTableLimit);
    1726           1 :             oResult->LimitRowCount(nTableLimit);
    1727             :         }
    1728             : 
    1729         977 :         if (oResult->RowCount() > 0)
    1730             :         {
    1731         863 :             bRet = TRUE;
    1732             : 
    1733        1726 :             m_papoLayers = static_cast<OGRGeoPackageTableLayer **>(CPLMalloc(
    1734         863 :                 sizeof(OGRGeoPackageTableLayer *) * oResult->RowCount()));
    1735             : 
    1736        1726 :             std::map<std::string, int> oMapTableRefCount;
    1737        3908 :             for (int i = 0; i < oResult->RowCount(); i++)
    1738             :             {
    1739        3045 :                 const char *pszTableName = oResult->GetValue(0, i);
    1740        3045 :                 if (pszTableName == nullptr)
    1741           0 :                     continue;
    1742        3045 :                 if (++oMapTableRefCount[pszTableName] == 2)
    1743             :                 {
    1744             :                     // This should normally not happen if all constraints are
    1745             :                     // properly set
    1746           2 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1747             :                              "Table %s appearing several times in "
    1748             :                              "gpkg_contents and/or gpkg_geometry_columns",
    1749             :                              pszTableName);
    1750             :                 }
    1751             :             }
    1752             : 
    1753        1726 :             std::set<std::string> oExistingLayers;
    1754        3908 :             for (int i = 0; i < oResult->RowCount(); i++)
    1755             :             {
    1756        3045 :                 const char *pszTableName = oResult->GetValue(0, i);
    1757        3045 :                 if (pszTableName == nullptr)
    1758           2 :                     continue;
    1759             :                 const bool bTableHasSeveralGeomColumns =
    1760        3045 :                     oMapTableRefCount[pszTableName] > 1;
    1761        3045 :                 bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
    1762        3045 :                 const char *pszGeomColName = oResult->GetValue(3, i);
    1763        3045 :                 const char *pszGeomType = oResult->GetValue(4, i);
    1764        3045 :                 const char *pszZ = oResult->GetValue(5, i);
    1765        3045 :                 const char *pszM = oResult->GetValue(6, i);
    1766             :                 bool bIsInGpkgContents =
    1767        3045 :                     CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
    1768        3045 :                 if (!bIsInGpkgContents)
    1769          44 :                     m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
    1770        3045 :                 const char *pszObjectType = oResult->GetValue(12, i);
    1771        3045 :                 if (pszObjectType == nullptr ||
    1772        3044 :                     !(EQUAL(pszObjectType, "table") ||
    1773          21 :                       EQUAL(pszObjectType, "view")))
    1774             :                 {
    1775           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1776             :                              "Table/view %s is referenced in gpkg_contents, "
    1777             :                              "but does not exist",
    1778             :                              pszTableName);
    1779           1 :                     continue;
    1780             :                 }
    1781             :                 // Non-standard and undocumented behavior:
    1782             :                 // if the same table appears to have several geometry columns,
    1783             :                 // handle it for now as multiple layers named
    1784             :                 // "table_name (geom_col_name)"
    1785             :                 // The way we handle that might change in the future (e.g
    1786             :                 // could be a single layer with multiple geometry columns)
    1787             :                 const std::string osLayerNameWithGeomColName =
    1788        5858 :                     pszGeomColName ? std::string(pszTableName) + " (" +
    1789             :                                          pszGeomColName + ')'
    1790        6088 :                                    : std::string(pszTableName);
    1791        3044 :                 if (cpl::contains(oExistingLayers, osLayerNameWithGeomColName))
    1792           1 :                     continue;
    1793        3043 :                 oExistingLayers.insert(osLayerNameWithGeomColName);
    1794             :                 const std::string osLayerName = bTableHasSeveralGeomColumns
    1795             :                                                     ? osLayerNameWithGeomColName
    1796        3043 :                                                     : std::string(pszTableName);
    1797             :                 OGRGeoPackageTableLayer *poLayer =
    1798        3043 :                     new OGRGeoPackageTableLayer(this, osLayerName.c_str());
    1799        3043 :                 bool bHasZ = pszZ && atoi(pszZ) > 0;
    1800        3043 :                 bool bHasM = pszM && atoi(pszM) > 0;
    1801        3043 :                 if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
    1802             :                 {
    1803         602 :                     if (pszZ && atoi(pszZ) == 2)
    1804           7 :                         bHasZ = false;
    1805         602 :                     if (pszM && atoi(pszM) == 2)
    1806           6 :                         bHasM = false;
    1807             :                 }
    1808        3043 :                 poLayer->SetOpeningParameters(
    1809             :                     pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
    1810             :                     pszGeomColName, pszGeomType, bHasZ, bHasM);
    1811        3043 :                 m_papoLayers[m_nLayers++] = poLayer;
    1812             :             }
    1813             :         }
    1814             :     }
    1815             : 
    1816        1163 :     bool bHasTileMatrixSet = false;
    1817        1163 :     if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
    1818             :     {
    1819         557 :         bHasTileMatrixSet = SQLGetInteger(hDB,
    1820             :                                           "SELECT 1 FROM sqlite_master WHERE "
    1821             :                                           "name = 'gpkg_tile_matrix_set' AND "
    1822             :                                           "type IN ('table', 'view')",
    1823             :                                           nullptr) == 1;
    1824             :     }
    1825        1163 :     if (bHasTileMatrixSet)
    1826             :     {
    1827             :         std::string osSQL =
    1828             :             "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
    1829             :             "c.min_x, c.min_y, c.max_x, c.max_y, "
    1830             :             "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
    1831             :             "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
    1832             :             "c.table_name = tms.table_name WHERE "
    1833         556 :             "data_type IN ('tiles', '2d-gridded-coverage')";
    1834         556 :         if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
    1835             :             osSubdatasetTableName =
    1836           2 :                 CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
    1837         556 :         if (!osSubdatasetTableName.empty())
    1838             :         {
    1839          14 :             char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
    1840             :                                            osSubdatasetTableName.c_str());
    1841          14 :             osSQL += pszTmp;
    1842          14 :             sqlite3_free(pszTmp);
    1843          14 :             SetPhysicalFilename(osFilename.c_str());
    1844             :         }
    1845         556 :         const int nTableLimit = GetOGRTableLimit();
    1846         556 :         if (nTableLimit > 0)
    1847             :         {
    1848         556 :             osSQL += " LIMIT ";
    1849         556 :             osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1850             :         }
    1851             : 
    1852         556 :         auto oResult = SQLQuery(hDB, osSQL.c_str());
    1853         556 :         if (!oResult)
    1854             :         {
    1855           0 :             return FALSE;
    1856             :         }
    1857             : 
    1858         556 :         if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
    1859             :         {
    1860           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1861             :                      "Cannot find table '%s' in GeoPackage dataset",
    1862             :                      osSubdatasetTableName.c_str());
    1863             :         }
    1864         555 :         else if (oResult->RowCount() == 1)
    1865             :         {
    1866         272 :             const char *pszTableName = oResult->GetValue(0, 0);
    1867         272 :             const char *pszIdentifier = oResult->GetValue(1, 0);
    1868         272 :             const char *pszDescription = oResult->GetValue(2, 0);
    1869         272 :             const char *pszSRSId = oResult->GetValue(3, 0);
    1870         272 :             const char *pszMinX = oResult->GetValue(4, 0);
    1871         272 :             const char *pszMinY = oResult->GetValue(5, 0);
    1872         272 :             const char *pszMaxX = oResult->GetValue(6, 0);
    1873         272 :             const char *pszMaxY = oResult->GetValue(7, 0);
    1874         272 :             const char *pszTMSMinX = oResult->GetValue(8, 0);
    1875         272 :             const char *pszTMSMinY = oResult->GetValue(9, 0);
    1876         272 :             const char *pszTMSMaxX = oResult->GetValue(10, 0);
    1877         272 :             const char *pszTMSMaxY = oResult->GetValue(11, 0);
    1878         272 :             const char *pszDataType = oResult->GetValue(12, 0);
    1879         272 :             if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
    1880             :                 pszTMSMaxY)
    1881             :             {
    1882         544 :                 bRet = OpenRaster(
    1883             :                     pszTableName, pszIdentifier, pszDescription,
    1884         272 :                     pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
    1885             :                     CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
    1886             :                     CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
    1887         272 :                     EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
    1888             :             }
    1889             :         }
    1890         283 :         else if (oResult->RowCount() >= 1)
    1891             :         {
    1892           5 :             bRet = TRUE;
    1893             : 
    1894           5 :             if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
    1895             :             {
    1896           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1897             :                          "File has more than %d raster tables. "
    1898             :                          "Limiting to first %d (can be overridden with "
    1899             :                          "OGR_TABLE_LIMIT config option)",
    1900             :                          nTableLimit, nTableLimit);
    1901           1 :                 oResult->LimitRowCount(nTableLimit);
    1902             :             }
    1903             : 
    1904           5 :             int nSDSCount = 0;
    1905        2013 :             for (int i = 0; i < oResult->RowCount(); i++)
    1906             :             {
    1907        2008 :                 const char *pszTableName = oResult->GetValue(0, i);
    1908        2008 :                 const char *pszIdentifier = oResult->GetValue(1, i);
    1909        2008 :                 if (pszTableName == nullptr)
    1910           0 :                     continue;
    1911             :                 m_aosSubDatasets.AddNameValue(
    1912             :                     CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
    1913        2008 :                     CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
    1914             :                 m_aosSubDatasets.AddNameValue(
    1915             :                     CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
    1916             :                     pszIdentifier
    1917        2008 :                         ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
    1918        4016 :                         : pszTableName);
    1919        2008 :                 nSDSCount++;
    1920             :             }
    1921             :         }
    1922             :     }
    1923             : 
    1924        1163 :     if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
    1925             :     {
    1926          30 :         if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
    1927             :         {
    1928          20 :             bRet = TRUE;
    1929             :         }
    1930             :         else
    1931             :         {
    1932          10 :             CPLDebug("GPKG",
    1933             :                      "This GeoPackage has no vector content and is opened "
    1934             :                      "in read-only mode. If you open it in update mode, "
    1935             :                      "opening will be successful.");
    1936             :         }
    1937             :     }
    1938             : 
    1939        1163 :     if (eAccess == GA_Update)
    1940             :     {
    1941         223 :         FixupWrongRTreeTrigger();
    1942         223 :         FixupWrongMedataReferenceColumnNameUpdate();
    1943             :     }
    1944             : 
    1945        1163 :     SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    1946             : 
    1947        1163 :     return bRet;
    1948             : }
    1949             : 
    1950             : /************************************************************************/
    1951             : /*                    DetectSpatialRefSysColumns()                      */
    1952             : /************************************************************************/
    1953             : 
    1954        1171 : void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
    1955             : {
    1956             :     // Detect definition_12_063 column
    1957             :     {
    1958        1171 :         sqlite3_stmt *hSQLStmt = nullptr;
    1959        1171 :         int rc = sqlite3_prepare_v2(
    1960             :             hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
    1961             :             &hSQLStmt, nullptr);
    1962        1171 :         if (rc == SQLITE_OK)
    1963             :         {
    1964          80 :             m_bHasDefinition12_063 = true;
    1965          80 :             sqlite3_finalize(hSQLStmt);
    1966             :         }
    1967             :     }
    1968             : 
    1969             :     // Detect epoch column
    1970        1171 :     if (m_bHasDefinition12_063)
    1971             :     {
    1972          80 :         sqlite3_stmt *hSQLStmt = nullptr;
    1973             :         int rc =
    1974          80 :             sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
    1975             :                                -1, &hSQLStmt, nullptr);
    1976          80 :         if (rc == SQLITE_OK)
    1977             :         {
    1978           5 :             m_bHasEpochColumn = true;
    1979           5 :             sqlite3_finalize(hSQLStmt);
    1980             :         }
    1981             :     }
    1982        1171 : }
    1983             : 
    1984             : /************************************************************************/
    1985             : /*                    FixupWrongRTreeTrigger()                          */
    1986             : /************************************************************************/
    1987             : 
    1988         223 : void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
    1989             : {
    1990             :     auto oResult = SQLQuery(
    1991             :         hDB,
    1992             :         "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
    1993         223 :         "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
    1994         223 :     if (oResult == nullptr)
    1995           0 :         return;
    1996         223 :     if (oResult->RowCount() > 0)
    1997             :     {
    1998           1 :         CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
    1999             :     }
    2000         225 :     for (int i = 0; i < oResult->RowCount(); i++)
    2001             :     {
    2002           2 :         const char *pszName = oResult->GetValue(0, i);
    2003           2 :         const char *pszSQL = oResult->GetValue(1, i);
    2004           2 :         const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
    2005           2 :         if (pszPtr1)
    2006             :         {
    2007           2 :             const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
    2008             :             // Skipping over geometry column name
    2009           4 :             while (*pszPtr == ' ')
    2010           2 :                 pszPtr++;
    2011           2 :             if (pszPtr[0] == '"' || pszPtr[0] == '\'')
    2012             :             {
    2013           1 :                 char chStringDelim = pszPtr[0];
    2014           1 :                 pszPtr++;
    2015           9 :                 while (*pszPtr != '\0' && *pszPtr != chStringDelim)
    2016             :                 {
    2017           8 :                     if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
    2018           0 :                         pszPtr += 2;
    2019             :                     else
    2020           8 :                         pszPtr += 1;
    2021             :                 }
    2022           1 :                 if (*pszPtr == chStringDelim)
    2023           1 :                     pszPtr++;
    2024             :             }
    2025             :             else
    2026             :             {
    2027           1 :                 pszPtr++;
    2028           8 :                 while (*pszPtr != ' ')
    2029           7 :                     pszPtr++;
    2030             :             }
    2031           2 :             if (*pszPtr == ' ')
    2032             :             {
    2033           2 :                 SQLCommand(hDB,
    2034           4 :                            ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
    2035             :                                .c_str());
    2036           4 :                 CPLString newSQL;
    2037           2 :                 newSQL.assign(pszSQL, pszPtr1 - pszSQL);
    2038           2 :                 newSQL += " AFTER UPDATE";
    2039           2 :                 newSQL += pszPtr;
    2040           2 :                 SQLCommand(hDB, newSQL);
    2041             :             }
    2042             :         }
    2043             :     }
    2044             : }
    2045             : 
    2046             : /************************************************************************/
    2047             : /*             FixupWrongMedataReferenceColumnNameUpdate()              */
    2048             : /************************************************************************/
    2049             : 
    2050         223 : void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
    2051             : {
    2052             :     // Fix wrong trigger that was generated by GDAL < 2.4.0
    2053             :     // See https://github.com/qgis/QGIS/issues/42768
    2054             :     auto oResult = SQLQuery(
    2055             :         hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
    2056             :              "NAME ='gpkg_metadata_reference_column_name_update' AND "
    2057         223 :              "sql LIKE '%column_nameIS%'");
    2058         223 :     if (oResult == nullptr)
    2059           0 :         return;
    2060         223 :     if (oResult->RowCount() == 1)
    2061             :     {
    2062           1 :         CPLDebug("GPKG", "Fixing incorrect trigger "
    2063             :                          "gpkg_metadata_reference_column_name_update");
    2064           1 :         const char *pszSQL = oResult->GetValue(0, 0);
    2065             :         std::string osNewSQL(
    2066           3 :             CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
    2067             : 
    2068           1 :         SQLCommand(hDB,
    2069             :                    "DROP TRIGGER gpkg_metadata_reference_column_name_update");
    2070           1 :         SQLCommand(hDB, osNewSQL.c_str());
    2071             :     }
    2072             : }
    2073             : 
    2074             : /************************************************************************/
    2075             : /*                  ClearCachedRelationships()                          */
    2076             : /************************************************************************/
    2077             : 
    2078          36 : void GDALGeoPackageDataset::ClearCachedRelationships()
    2079             : {
    2080          36 :     m_bHasPopulatedRelationships = false;
    2081          36 :     m_osMapRelationships.clear();
    2082          36 : }
    2083             : 
    2084             : /************************************************************************/
    2085             : /*                           LoadRelationships()                        */
    2086             : /************************************************************************/
    2087             : 
    2088          80 : void GDALGeoPackageDataset::LoadRelationships() const
    2089             : {
    2090          80 :     m_osMapRelationships.clear();
    2091             : 
    2092          80 :     std::vector<std::string> oExcludedTables;
    2093          80 :     if (HasGpkgextRelationsTable())
    2094             :     {
    2095          37 :         LoadRelationshipsUsingRelatedTablesExtension();
    2096             : 
    2097          89 :         for (const auto &oRelationship : m_osMapRelationships)
    2098             :         {
    2099             :             oExcludedTables.emplace_back(
    2100          52 :                 oRelationship.second->GetMappingTableName());
    2101             :         }
    2102             :     }
    2103             : 
    2104             :     // Also load relationships defined using foreign keys (i.e. one-to-many
    2105             :     // relationships). Here we must exclude any relationships defined from the
    2106             :     // related tables extension, we don't want them included twice.
    2107          80 :     LoadRelationshipsFromForeignKeys(oExcludedTables);
    2108          80 :     m_bHasPopulatedRelationships = true;
    2109          80 : }
    2110             : 
    2111             : /************************************************************************/
    2112             : /*         LoadRelationshipsUsingRelatedTablesExtension()               */
    2113             : /************************************************************************/
    2114             : 
    2115          37 : void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
    2116             : {
    2117          37 :     m_osMapRelationships.clear();
    2118             : 
    2119             :     auto oResultTable = SQLQuery(
    2120          37 :         hDB, "SELECT base_table_name, base_primary_column, "
    2121             :              "related_table_name, related_primary_column, relation_name, "
    2122          74 :              "mapping_table_name FROM gpkgext_relations");
    2123          37 :     if (oResultTable && oResultTable->RowCount() > 0)
    2124             :     {
    2125          86 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    2126             :         {
    2127          53 :             const char *pszBaseTableName = oResultTable->GetValue(0, i);
    2128          53 :             if (!pszBaseTableName)
    2129             :             {
    2130           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2131             :                          "Could not retrieve base_table_name from "
    2132             :                          "gpkgext_relations");
    2133           1 :                 continue;
    2134             :             }
    2135          53 :             const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
    2136          53 :             if (!pszBasePrimaryColumn)
    2137             :             {
    2138           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2139             :                          "Could not retrieve base_primary_column from "
    2140             :                          "gpkgext_relations");
    2141           0 :                 continue;
    2142             :             }
    2143          53 :             const char *pszRelatedTableName = oResultTable->GetValue(2, i);
    2144          53 :             if (!pszRelatedTableName)
    2145             :             {
    2146           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2147             :                          "Could not retrieve related_table_name from "
    2148             :                          "gpkgext_relations");
    2149           0 :                 continue;
    2150             :             }
    2151          53 :             const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
    2152          53 :             if (!pszRelatedPrimaryColumn)
    2153             :             {
    2154           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2155             :                          "Could not retrieve related_primary_column from "
    2156             :                          "gpkgext_relations");
    2157           0 :                 continue;
    2158             :             }
    2159          53 :             const char *pszRelationName = oResultTable->GetValue(4, i);
    2160          53 :             if (!pszRelationName)
    2161             :             {
    2162           0 :                 CPLError(
    2163             :                     CE_Warning, CPLE_AppDefined,
    2164             :                     "Could not retrieve relation_name from gpkgext_relations");
    2165           0 :                 continue;
    2166             :             }
    2167          53 :             const char *pszMappingTableName = oResultTable->GetValue(5, i);
    2168          53 :             if (!pszMappingTableName)
    2169             :             {
    2170           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2171             :                          "Could not retrieve mapping_table_name from "
    2172             :                          "gpkgext_relations");
    2173           0 :                 continue;
    2174             :             }
    2175             : 
    2176             :             // confirm that mapping table exists
    2177             :             char *pszSQL =
    2178          53 :                 sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
    2179             :                                 "name='%q' AND type IN ('table', 'view')",
    2180             :                                 pszMappingTableName);
    2181          53 :             const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
    2182          53 :             sqlite3_free(pszSQL);
    2183             : 
    2184          55 :             if (nMappingTableCount < 1 &&
    2185           2 :                 !const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
    2186           2 :                     pszMappingTableName))
    2187             :             {
    2188           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2189             :                          "Relationship mapping table %s does not exist",
    2190             :                          pszMappingTableName);
    2191           1 :                 continue;
    2192             :             }
    2193             : 
    2194             :             const std::string osRelationName = GenerateNameForRelationship(
    2195         104 :                 pszBaseTableName, pszRelatedTableName, pszRelationName);
    2196             : 
    2197         104 :             std::string osType{};
    2198             :             // defined requirement classes -- for these types the relation name
    2199             :             // will be specific string value from the related tables extension.
    2200             :             // In this case we need to construct a unique relationship name
    2201             :             // based on the related tables
    2202          52 :             if (EQUAL(pszRelationName, "media") ||
    2203          40 :                 EQUAL(pszRelationName, "simple_attributes") ||
    2204          40 :                 EQUAL(pszRelationName, "features") ||
    2205          18 :                 EQUAL(pszRelationName, "attributes") ||
    2206           2 :                 EQUAL(pszRelationName, "tiles"))
    2207             :             {
    2208          50 :                 osType = pszRelationName;
    2209             :             }
    2210             :             else
    2211             :             {
    2212             :                 // user defined types default to features
    2213           2 :                 osType = "features";
    2214             :             }
    2215             : 
    2216             :             std::unique_ptr<GDALRelationship> poRelationship(
    2217             :                 new GDALRelationship(osRelationName, pszBaseTableName,
    2218         156 :                                      pszRelatedTableName, GRC_MANY_TO_MANY));
    2219             : 
    2220         104 :             poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
    2221         104 :             poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
    2222         104 :             poRelationship->SetLeftMappingTableFields({"base_id"});
    2223         104 :             poRelationship->SetRightMappingTableFields({"related_id"});
    2224          52 :             poRelationship->SetMappingTableName(pszMappingTableName);
    2225          52 :             poRelationship->SetRelatedTableType(osType);
    2226             : 
    2227          52 :             m_osMapRelationships[osRelationName] = std::move(poRelationship);
    2228             :         }
    2229             :     }
    2230          37 : }
    2231             : 
    2232             : /************************************************************************/
    2233             : /*                GenerateNameForRelationship()                         */
    2234             : /************************************************************************/
    2235             : 
    2236          76 : std::string GDALGeoPackageDataset::GenerateNameForRelationship(
    2237             :     const char *pszBaseTableName, const char *pszRelatedTableName,
    2238             :     const char *pszType)
    2239             : {
    2240             :     // defined requirement classes -- for these types the relation name will be
    2241             :     // specific string value from the related tables extension. In this case we
    2242             :     // need to construct a unique relationship name based on the related tables
    2243          76 :     if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
    2244          53 :         EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
    2245           8 :         EQUAL(pszType, "tiles"))
    2246             :     {
    2247         136 :         std::ostringstream stream;
    2248             :         stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
    2249          68 :                << pszType;
    2250          68 :         return stream.str();
    2251             :     }
    2252             :     else
    2253             :     {
    2254             :         // user defined types default to features
    2255           8 :         return pszType;
    2256             :     }
    2257             : }
    2258             : 
    2259             : /************************************************************************/
    2260             : /*                       ValidateRelationship()                         */
    2261             : /************************************************************************/
    2262             : 
    2263          28 : bool GDALGeoPackageDataset::ValidateRelationship(
    2264             :     const GDALRelationship *poRelationship, std::string &failureReason)
    2265             : {
    2266             : 
    2267          28 :     if (poRelationship->GetCardinality() !=
    2268             :         GDALRelationshipCardinality::GRC_MANY_TO_MANY)
    2269             :     {
    2270           3 :         failureReason = "Only many to many relationships are supported";
    2271           3 :         return false;
    2272             :     }
    2273             : 
    2274          50 :     std::string osRelatedTableType = poRelationship->GetRelatedTableType();
    2275          65 :     if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
    2276          30 :         osRelatedTableType != "media" &&
    2277          20 :         osRelatedTableType != "simple_attributes" &&
    2278          55 :         osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
    2279             :     {
    2280             :         failureReason =
    2281           4 :             ("Related table type " + osRelatedTableType +
    2282             :              " is not a valid value for the GeoPackage specification. "
    2283             :              "Valid values are: features, media, simple_attributes, "
    2284             :              "attributes, tiles.")
    2285           2 :                 .c_str();
    2286           2 :         return false;
    2287             :     }
    2288             : 
    2289          23 :     const std::string &osLeftTableName = poRelationship->GetLeftTableName();
    2290          23 :     OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
    2291          23 :         GetLayerByName(osLeftTableName.c_str()));
    2292          23 :     if (!poLeftTable)
    2293             :     {
    2294           4 :         failureReason = ("Left table " + osLeftTableName +
    2295             :                          " is not an existing layer in the dataset")
    2296           2 :                             .c_str();
    2297           2 :         return false;
    2298             :     }
    2299          21 :     const std::string &osRightTableName = poRelationship->GetRightTableName();
    2300          21 :     OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
    2301          21 :         GetLayerByName(osRightTableName.c_str()));
    2302          21 :     if (!poRightTable)
    2303             :     {
    2304           4 :         failureReason = ("Right table " + osRightTableName +
    2305             :                          " is not an existing layer in the dataset")
    2306           2 :                             .c_str();
    2307           2 :         return false;
    2308             :     }
    2309             : 
    2310          19 :     const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
    2311          19 :     if (aosLeftTableFields.empty())
    2312             :     {
    2313           1 :         failureReason = "No left table fields were specified";
    2314           1 :         return false;
    2315             :     }
    2316          18 :     else if (aosLeftTableFields.size() > 1)
    2317             :     {
    2318             :         failureReason = "Only a single left table field is permitted for the "
    2319           1 :                         "GeoPackage specification";
    2320           1 :         return false;
    2321             :     }
    2322             :     else
    2323             :     {
    2324             :         // validate left field exists
    2325          34 :         if (poLeftTable->GetLayerDefn()->GetFieldIndex(
    2326          37 :                 aosLeftTableFields[0].c_str()) < 0 &&
    2327           3 :             !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
    2328             :         {
    2329           2 :             failureReason = ("Left table field " + aosLeftTableFields[0] +
    2330           2 :                              " does not exist in " + osLeftTableName)
    2331           1 :                                 .c_str();
    2332           1 :             return false;
    2333             :         }
    2334             :     }
    2335             : 
    2336          16 :     const auto &aosRightTableFields = poRelationship->GetRightTableFields();
    2337          16 :     if (aosRightTableFields.empty())
    2338             :     {
    2339           1 :         failureReason = "No right table fields were specified";
    2340           1 :         return false;
    2341             :     }
    2342          15 :     else if (aosRightTableFields.size() > 1)
    2343             :     {
    2344             :         failureReason = "Only a single right table field is permitted for the "
    2345           1 :                         "GeoPackage specification";
    2346           1 :         return false;
    2347             :     }
    2348             :     else
    2349             :     {
    2350             :         // validate right field exists
    2351          28 :         if (poRightTable->GetLayerDefn()->GetFieldIndex(
    2352          32 :                 aosRightTableFields[0].c_str()) < 0 &&
    2353           4 :             !EQUAL(poRightTable->GetFIDColumn(),
    2354             :                    aosRightTableFields[0].c_str()))
    2355             :         {
    2356           4 :             failureReason = ("Right table field " + aosRightTableFields[0] +
    2357           4 :                              " does not exist in " + osRightTableName)
    2358           2 :                                 .c_str();
    2359           2 :             return false;
    2360             :         }
    2361             :     }
    2362             : 
    2363          12 :     return true;
    2364             : }
    2365             : 
    2366             : /************************************************************************/
    2367             : /*                         InitRaster()                                 */
    2368             : /************************************************************************/
    2369             : 
    2370         356 : bool GDALGeoPackageDataset::InitRaster(
    2371             :     GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
    2372             :     double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
    2373             :     const char *pszContentsMinY, const char *pszContentsMaxX,
    2374             :     const char *pszContentsMaxY, char **papszOpenOptionsIn,
    2375             :     const SQLResult &oResult, int nIdxInResult)
    2376             : {
    2377         356 :     m_osRasterTable = pszTableName;
    2378         356 :     m_dfTMSMinX = dfMinX;
    2379         356 :     m_dfTMSMaxY = dfMaxY;
    2380             : 
    2381             :     // Despite prior checking, the type might be Binary and
    2382             :     // SQLResultGetValue() not working properly on it
    2383         356 :     int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
    2384         356 :     if (nZoomLevel < 0 || nZoomLevel > 65536)
    2385             :     {
    2386           0 :         return false;
    2387             :     }
    2388         356 :     double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
    2389         356 :     double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
    2390         356 :     if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
    2391             :     {
    2392           0 :         return false;
    2393             :     }
    2394         356 :     int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
    2395         356 :     int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
    2396         356 :     if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
    2397             :         nTileHeight > 65536)
    2398             :     {
    2399           0 :         return false;
    2400             :     }
    2401             :     int nTileMatrixWidth = static_cast<int>(
    2402         712 :         std::min(static_cast<GIntBig>(INT_MAX),
    2403         356 :                  CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
    2404             :     int nTileMatrixHeight = static_cast<int>(
    2405         712 :         std::min(static_cast<GIntBig>(INT_MAX),
    2406         356 :                  CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
    2407         356 :     if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
    2408             :     {
    2409           0 :         return false;
    2410             :     }
    2411             : 
    2412             :     /* Use content bounds in priority over tile_matrix_set bounds */
    2413         356 :     double dfGDALMinX = dfMinX;
    2414         356 :     double dfGDALMinY = dfMinY;
    2415         356 :     double dfGDALMaxX = dfMaxX;
    2416         356 :     double dfGDALMaxY = dfMaxY;
    2417             :     pszContentsMinX =
    2418         356 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
    2419             :     pszContentsMinY =
    2420         356 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
    2421             :     pszContentsMaxX =
    2422         356 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
    2423             :     pszContentsMaxY =
    2424         356 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
    2425         356 :     if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
    2426         356 :         pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
    2427             :     {
    2428         711 :         if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
    2429         355 :             CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
    2430             :         {
    2431         355 :             dfGDALMinX = CPLAtof(pszContentsMinX);
    2432         355 :             dfGDALMinY = CPLAtof(pszContentsMinY);
    2433         355 :             dfGDALMaxX = CPLAtof(pszContentsMaxX);
    2434         355 :             dfGDALMaxY = CPLAtof(pszContentsMaxY);
    2435             :         }
    2436             :         else
    2437             :         {
    2438           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    2439             :                      "Illegal min_x/min_y/max_x/max_y values for %s in open "
    2440             :                      "options and/or gpkg_contents. Using bounds of "
    2441             :                      "gpkg_tile_matrix_set instead",
    2442             :                      pszTableName);
    2443             :         }
    2444             :     }
    2445         356 :     if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
    2446             :     {
    2447           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2448             :                  "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
    2449           0 :         return false;
    2450             :     }
    2451             : 
    2452         356 :     int nBandCount = 0;
    2453             :     const char *pszBAND_COUNT =
    2454         356 :         CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
    2455         356 :     if (poParentDS)
    2456             :     {
    2457          86 :         nBandCount = poParentDS->GetRasterCount();
    2458             :     }
    2459         270 :     else if (m_eDT != GDT_Byte)
    2460             :     {
    2461          65 :         if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
    2462           0 :             !EQUAL(pszBAND_COUNT, "1"))
    2463             :         {
    2464           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    2465             :                      "BAND_COUNT ignored for non-Byte data");
    2466             :         }
    2467          65 :         nBandCount = 1;
    2468             :     }
    2469             :     else
    2470             :     {
    2471         205 :         if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
    2472             :         {
    2473          69 :             nBandCount = atoi(pszBAND_COUNT);
    2474          69 :             if (nBandCount == 1)
    2475           5 :                 GetMetadata("IMAGE_STRUCTURE");
    2476             :         }
    2477             :         else
    2478             :         {
    2479         136 :             GetMetadata("IMAGE_STRUCTURE");
    2480         136 :             nBandCount = m_nBandCountFromMetadata;
    2481         136 :             if (nBandCount == 1)
    2482          39 :                 m_eTF = GPKG_TF_PNG;
    2483             :         }
    2484         205 :         if (nBandCount == 1 && !m_osTFFromMetadata.empty())
    2485             :         {
    2486           2 :             m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
    2487             :         }
    2488         205 :         if (nBandCount <= 0 || nBandCount > 4)
    2489          83 :             nBandCount = 4;
    2490             :     }
    2491             : 
    2492         356 :     return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
    2493             :                       dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
    2494             :                       nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
    2495         356 :                       dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
    2496             : }
    2497             : 
    2498             : /************************************************************************/
    2499             : /*                      ComputeTileAndPixelShifts()                     */
    2500             : /************************************************************************/
    2501             : 
    2502         776 : bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
    2503             : {
    2504             :     int nTileWidth, nTileHeight;
    2505         776 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    2506             : 
    2507             :     // Compute shift between GDAL origin and TileMatrixSet origin
    2508         776 :     const double dfShiftXPixels =
    2509         776 :         (m_adfGeoTransform[0] - m_dfTMSMinX) / m_adfGeoTransform[1];
    2510         776 :     if (dfShiftXPixels / nTileWidth <= INT_MIN ||
    2511         774 :         dfShiftXPixels / nTileWidth > INT_MAX)
    2512           2 :         return false;
    2513         774 :     const int64_t nShiftXPixels =
    2514         774 :         static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
    2515         774 :     m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
    2516         774 :     if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
    2517          11 :         m_nShiftXTiles--;
    2518         774 :     m_nShiftXPixelsMod =
    2519         774 :         (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
    2520             :         nTileWidth;
    2521             : 
    2522         774 :     const double dfShiftYPixels =
    2523         774 :         (m_adfGeoTransform[3] - m_dfTMSMaxY) / m_adfGeoTransform[5];
    2524         774 :     if (dfShiftYPixels / nTileHeight <= INT_MIN ||
    2525         774 :         dfShiftYPixels / nTileHeight > INT_MAX)
    2526           1 :         return false;
    2527         773 :     const int64_t nShiftYPixels =
    2528         773 :         static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
    2529         773 :     m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
    2530         773 :     if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
    2531          11 :         m_nShiftYTiles--;
    2532         773 :     m_nShiftYPixelsMod =
    2533         773 :         (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
    2534             :         nTileHeight;
    2535         773 :     return true;
    2536             : }
    2537             : 
    2538             : /************************************************************************/
    2539             : /*                            AllocCachedTiles()                        */
    2540             : /************************************************************************/
    2541             : 
    2542         773 : bool GDALGeoPackageDataset::AllocCachedTiles()
    2543             : {
    2544             :     int nTileWidth, nTileHeight;
    2545         773 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    2546             : 
    2547             :     // We currently need 4 caches because of
    2548             :     // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
    2549         773 :     const int nCacheCount = 4;
    2550             :     /*
    2551             :             (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
    2552             :             (GetUpdate() && m_eDT == GDT_Byte) ? 2 : 1;
    2553             :     */
    2554         773 :     m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
    2555             :         cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_Byte ? 4 : 1) *
    2556             :                           m_nDTSize),
    2557             :         nTileWidth, nTileHeight));
    2558         773 :     if (m_pabyCachedTiles == nullptr)
    2559             :     {
    2560           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
    2561             :                  nTileWidth, nTileHeight);
    2562           0 :         return false;
    2563             :     }
    2564             : 
    2565         773 :     return true;
    2566             : }
    2567             : 
    2568             : /************************************************************************/
    2569             : /*                         InitRaster()                                 */
    2570             : /************************************************************************/
    2571             : 
    2572         595 : bool GDALGeoPackageDataset::InitRaster(
    2573             :     GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
    2574             :     int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
    2575             :     double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
    2576             :     int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
    2577             :     double dfGDALMaxX, double dfGDALMaxY)
    2578             : {
    2579         595 :     m_osRasterTable = pszTableName;
    2580         595 :     m_dfTMSMinX = dfTMSMinX;
    2581         595 :     m_dfTMSMaxY = dfTMSMaxY;
    2582         595 :     m_nZoomLevel = nZoomLevel;
    2583         595 :     m_nTileMatrixWidth = nTileMatrixWidth;
    2584         595 :     m_nTileMatrixHeight = nTileMatrixHeight;
    2585             : 
    2586         595 :     m_bGeoTransformValid = true;
    2587         595 :     m_adfGeoTransform[0] = dfGDALMinX;
    2588         595 :     m_adfGeoTransform[1] = dfPixelXSize;
    2589         595 :     m_adfGeoTransform[3] = dfGDALMaxY;
    2590         595 :     m_adfGeoTransform[5] = -dfPixelYSize;
    2591         595 :     double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
    2592         595 :     double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
    2593         595 :     if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
    2594             :     {
    2595           0 :         CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
    2596             :                  dfRasterXSize, dfRasterYSize);
    2597           0 :         return false;
    2598             :     }
    2599         595 :     nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
    2600         595 :     nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
    2601             : 
    2602         595 :     if (poParentDS)
    2603             :     {
    2604         325 :         m_poParentDS = poParentDS;
    2605         325 :         eAccess = poParentDS->eAccess;
    2606         325 :         hDB = poParentDS->hDB;
    2607         325 :         m_eTF = poParentDS->m_eTF;
    2608         325 :         m_eDT = poParentDS->m_eDT;
    2609         325 :         m_nDTSize = poParentDS->m_nDTSize;
    2610         325 :         m_dfScale = poParentDS->m_dfScale;
    2611         325 :         m_dfOffset = poParentDS->m_dfOffset;
    2612         325 :         m_dfPrecision = poParentDS->m_dfPrecision;
    2613         325 :         m_usGPKGNull = poParentDS->m_usGPKGNull;
    2614         325 :         m_nQuality = poParentDS->m_nQuality;
    2615         325 :         m_nZLevel = poParentDS->m_nZLevel;
    2616         325 :         m_bDither = poParentDS->m_bDither;
    2617             :         /*m_nSRID = poParentDS->m_nSRID;*/
    2618         325 :         m_osWHERE = poParentDS->m_osWHERE;
    2619         325 :         SetDescription(CPLSPrintf("%s - zoom_level=%d",
    2620         325 :                                   poParentDS->GetDescription(), m_nZoomLevel));
    2621             :     }
    2622             : 
    2623        2081 :     for (int i = 1; i <= nBandCount; i++)
    2624             :     {
    2625             :         GDALGeoPackageRasterBand *poNewBand =
    2626        1486 :             new GDALGeoPackageRasterBand(this, nTileWidth, nTileHeight);
    2627        1486 :         if (poParentDS)
    2628             :         {
    2629         761 :             int bHasNoData = FALSE;
    2630             :             double dfNoDataValue =
    2631         761 :                 poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    2632         761 :             if (bHasNoData)
    2633          24 :                 poNewBand->SetNoDataValueInternal(dfNoDataValue);
    2634             :         }
    2635        1486 :         SetBand(i, poNewBand);
    2636             : 
    2637        1486 :         if (nBandCount == 1 && m_poCTFromMetadata)
    2638             :         {
    2639           3 :             poNewBand->AssignColorTable(m_poCTFromMetadata.get());
    2640             :         }
    2641        1486 :         if (!m_osNodataValueFromMetadata.empty())
    2642             :         {
    2643           8 :             poNewBand->SetNoDataValueInternal(
    2644             :                 CPLAtof(m_osNodataValueFromMetadata.c_str()));
    2645             :         }
    2646             :     }
    2647             : 
    2648         595 :     if (!ComputeTileAndPixelShifts())
    2649             :     {
    2650           3 :         CPLError(CE_Failure, CPLE_AppDefined,
    2651             :                  "Overflow occurred in ComputeTileAndPixelShifts()");
    2652           3 :         return false;
    2653             :     }
    2654             : 
    2655         592 :     GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    2656         592 :     GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
    2657             :                                     CPLSPrintf("%d", m_nZoomLevel));
    2658             : 
    2659         592 :     return AllocCachedTiles();
    2660             : }
    2661             : 
    2662             : /************************************************************************/
    2663             : /*                 GDALGPKGMBTilesGetTileFormat()                       */
    2664             : /************************************************************************/
    2665             : 
    2666          80 : GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
    2667             : {
    2668          80 :     GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
    2669          80 :     if (pszTF)
    2670             :     {
    2671          80 :         if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
    2672           1 :             eTF = GPKG_TF_PNG_JPEG;
    2673          79 :         else if (EQUAL(pszTF, "PNG"))
    2674          46 :             eTF = GPKG_TF_PNG;
    2675          33 :         else if (EQUAL(pszTF, "PNG8"))
    2676           6 :             eTF = GPKG_TF_PNG8;
    2677          27 :         else if (EQUAL(pszTF, "JPEG"))
    2678          14 :             eTF = GPKG_TF_JPEG;
    2679          13 :         else if (EQUAL(pszTF, "WEBP"))
    2680          13 :             eTF = GPKG_TF_WEBP;
    2681             :         else
    2682             :         {
    2683           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    2684             :                      "Unsuppoted value for TILE_FORMAT: %s", pszTF);
    2685             :         }
    2686             :     }
    2687          80 :     return eTF;
    2688             : }
    2689             : 
    2690          28 : const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
    2691             : {
    2692          28 :     switch (eTF)
    2693             :     {
    2694          26 :         case GPKG_TF_PNG:
    2695             :         case GPKG_TF_PNG8:
    2696          26 :             return "png";
    2697           1 :         case GPKG_TF_JPEG:
    2698           1 :             return "jpg";
    2699           1 :         case GPKG_TF_WEBP:
    2700           1 :             return "webp";
    2701           0 :         default:
    2702           0 :             break;
    2703             :     }
    2704           0 :     CPLError(CE_Failure, CPLE_NotSupported,
    2705             :              "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
    2706           0 :     return nullptr;
    2707             : }
    2708             : 
    2709             : /************************************************************************/
    2710             : /*                         OpenRaster()                                 */
    2711             : /************************************************************************/
    2712             : 
    2713         272 : bool GDALGeoPackageDataset::OpenRaster(
    2714             :     const char *pszTableName, const char *pszIdentifier,
    2715             :     const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
    2716             :     double dfMaxX, double dfMaxY, const char *pszContentsMinX,
    2717             :     const char *pszContentsMinY, const char *pszContentsMaxX,
    2718             :     const char *pszContentsMaxY, bool bIsTiles, char **papszOpenOptionsIn)
    2719             : {
    2720         272 :     if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
    2721           0 :         return false;
    2722             : 
    2723             :     // Config option just for debug, and for example force set to NaN
    2724             :     // which is not supported
    2725         544 :     CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
    2726         544 :     CPLString osUom;
    2727         544 :     CPLString osFieldName;
    2728         544 :     CPLString osGridCellEncoding;
    2729         272 :     if (!bIsTiles)
    2730             :     {
    2731          65 :         char *pszSQL = sqlite3_mprintf(
    2732             :             "SELECT datatype, scale, offset, data_null, precision FROM "
    2733             :             "gpkg_2d_gridded_coverage_ancillary "
    2734             :             "WHERE tile_matrix_set_name = '%q' "
    2735             :             "AND datatype IN ('integer', 'float')"
    2736             :             "AND (scale > 0 OR scale IS NULL)",
    2737             :             pszTableName);
    2738          65 :         auto oResult = SQLQuery(hDB, pszSQL);
    2739          65 :         sqlite3_free(pszSQL);
    2740          65 :         if (!oResult || oResult->RowCount() == 0)
    2741             :         {
    2742           0 :             return false;
    2743             :         }
    2744          65 :         const char *pszDataType = oResult->GetValue(0, 0);
    2745          65 :         const char *pszScale = oResult->GetValue(1, 0);
    2746          65 :         const char *pszOffset = oResult->GetValue(2, 0);
    2747          65 :         const char *pszDataNull = oResult->GetValue(3, 0);
    2748          65 :         const char *pszPrecision = oResult->GetValue(4, 0);
    2749          65 :         if (pszDataNull)
    2750          23 :             osDataNull = pszDataNull;
    2751          65 :         if (EQUAL(pszDataType, "float"))
    2752             :         {
    2753           6 :             SetDataType(GDT_Float32);
    2754           6 :             m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
    2755             :         }
    2756             :         else
    2757             :         {
    2758          59 :             SetDataType(GDT_Float32);
    2759          59 :             m_eTF = GPKG_TF_PNG_16BIT;
    2760          59 :             const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
    2761          59 :             const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
    2762          59 :             if (dfScale == 1.0)
    2763             :             {
    2764          59 :                 if (dfOffset == 0.0)
    2765             :                 {
    2766          24 :                     SetDataType(GDT_UInt16);
    2767             :                 }
    2768          35 :                 else if (dfOffset == -32768.0)
    2769             :                 {
    2770          35 :                     SetDataType(GDT_Int16);
    2771             :                 }
    2772             :                 // coverity[tainted_data]
    2773           0 :                 else if (dfOffset == -32767.0 && !osDataNull.empty() &&
    2774           0 :                          CPLAtof(osDataNull) == 65535.0)
    2775             :                 // Given that we will map the nodata value to -32768
    2776             :                 {
    2777           0 :                     SetDataType(GDT_Int16);
    2778             :                 }
    2779             :             }
    2780             : 
    2781             :             // Check that the tile offset and scales are compatible of a
    2782             :             // final integer result.
    2783          59 :             if (m_eDT != GDT_Float32)
    2784             :             {
    2785             :                 // coverity[tainted_data]
    2786          59 :                 if (dfScale == 1.0 && dfOffset == -32768.0 &&
    2787         118 :                     !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
    2788             :                 {
    2789             :                     // Given that we will map the nodata value to -32768
    2790           9 :                     pszSQL = sqlite3_mprintf(
    2791             :                         "SELECT 1 FROM "
    2792             :                         "gpkg_2d_gridded_tile_ancillary WHERE "
    2793             :                         "tpudt_name = '%q' "
    2794             :                         "AND NOT ((offset = 0.0 or offset = 1.0) "
    2795             :                         "AND scale = 1.0) "
    2796             :                         "LIMIT 1",
    2797             :                         pszTableName);
    2798             :                 }
    2799             :                 else
    2800             :                 {
    2801          50 :                     pszSQL = sqlite3_mprintf(
    2802             :                         "SELECT 1 FROM "
    2803             :                         "gpkg_2d_gridded_tile_ancillary WHERE "
    2804             :                         "tpudt_name = '%q' "
    2805             :                         "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
    2806             :                         pszTableName);
    2807             :                 }
    2808          59 :                 sqlite3_stmt *hSQLStmt = nullptr;
    2809             :                 int rc =
    2810          59 :                     sqlite3_prepare_v2(hDB, pszSQL, -1, &hSQLStmt, nullptr);
    2811             : 
    2812          59 :                 if (rc == SQLITE_OK)
    2813             :                 {
    2814          59 :                     if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
    2815             :                     {
    2816           8 :                         SetDataType(GDT_Float32);
    2817             :                     }
    2818          59 :                     sqlite3_finalize(hSQLStmt);
    2819             :                 }
    2820             :                 else
    2821             :                 {
    2822           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    2823             :                              "Error when running %s", pszSQL);
    2824             :                 }
    2825          59 :                 sqlite3_free(pszSQL);
    2826             :             }
    2827             : 
    2828          59 :             SetGlobalOffsetScale(dfOffset, dfScale);
    2829             :         }
    2830          65 :         if (pszPrecision)
    2831          65 :             m_dfPrecision = CPLAtof(pszPrecision);
    2832             : 
    2833             :         // Request those columns in a separate query, so as to keep
    2834             :         // compatibility with pre OGC 17-066r1 databases
    2835             :         pszSQL =
    2836          65 :             sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
    2837             :                             "gpkg_2d_gridded_coverage_ancillary "
    2838             :                             "WHERE tile_matrix_set_name = '%q'",
    2839             :                             pszTableName);
    2840          65 :         CPLPushErrorHandler(CPLQuietErrorHandler);
    2841          65 :         oResult = SQLQuery(hDB, pszSQL);
    2842          65 :         CPLPopErrorHandler();
    2843          65 :         sqlite3_free(pszSQL);
    2844          65 :         if (oResult && oResult->RowCount() == 1)
    2845             :         {
    2846          64 :             const char *pszUom = oResult->GetValue(0, 0);
    2847          64 :             if (pszUom)
    2848           2 :                 osUom = pszUom;
    2849          64 :             const char *pszFieldName = oResult->GetValue(1, 0);
    2850          64 :             if (pszFieldName)
    2851          64 :                 osFieldName = pszFieldName;
    2852          64 :             const char *pszGridCellEncoding = oResult->GetValue(2, 0);
    2853          64 :             if (pszGridCellEncoding)
    2854          64 :                 osGridCellEncoding = pszGridCellEncoding;
    2855             :         }
    2856             :     }
    2857             : 
    2858         272 :     m_bRecordInsertedInGPKGContent = true;
    2859         272 :     m_nSRID = nSRSId;
    2860             : 
    2861         543 :     if (auto poSRS = GetSpatialRef(nSRSId))
    2862             :     {
    2863         271 :         m_oSRS = *(poSRS.get());
    2864             :     }
    2865             : 
    2866             :     /* Various sanity checks added in the SELECT */
    2867         272 :     char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
    2868         544 :     CPLString osQuotedTableName(pszQuotedTableName);
    2869         272 :     sqlite3_free(pszQuotedTableName);
    2870         272 :     char *pszSQL = sqlite3_mprintf(
    2871             :         "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
    2872             :         "tile_height, matrix_width, matrix_height "
    2873             :         "FROM gpkg_tile_matrix tm "
    2874             :         "WHERE table_name = %s "
    2875             :         // INT_MAX would be the theoretical maximum value to avoid
    2876             :         // overflows, but that's already a insane value.
    2877             :         "AND zoom_level >= 0 AND zoom_level <= 65536 "
    2878             :         "AND pixel_x_size > 0 AND pixel_y_size > 0 "
    2879             :         "AND tile_width >= 1 AND tile_width <= 65536 "
    2880             :         "AND tile_height >= 1 AND tile_height <= 65536 "
    2881             :         "AND matrix_width >= 1 AND matrix_height >= 1",
    2882             :         osQuotedTableName.c_str());
    2883         544 :     CPLString osSQL(pszSQL);
    2884             :     const char *pszZoomLevel =
    2885         272 :         CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
    2886         272 :     if (pszZoomLevel)
    2887             :     {
    2888           5 :         if (GetUpdate())
    2889           1 :             osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
    2890             :         else
    2891             :         {
    2892             :             osSQL += CPLSPrintf(
    2893             :                 " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
    2894             :                 "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
    2895             :                 atoi(pszZoomLevel), atoi(pszZoomLevel),
    2896           4 :                 osQuotedTableName.c_str());
    2897             :         }
    2898             :     }
    2899             :     // In read-only mode, only lists non empty zoom levels
    2900         267 :     else if (!GetUpdate())
    2901             :     {
    2902             :         osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
    2903             :                             "tm.zoom_level LIMIT 1)",
    2904         213 :                             osQuotedTableName.c_str());
    2905             :     }
    2906             :     else  // if( pszZoomLevel == nullptr )
    2907             :     {
    2908             :         osSQL +=
    2909             :             CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
    2910          54 :                        osQuotedTableName.c_str());
    2911             :     }
    2912         272 :     osSQL += " ORDER BY zoom_level DESC";
    2913             :     // To avoid denial of service.
    2914         272 :     osSQL += " LIMIT 100";
    2915             : 
    2916         544 :     auto oResult = SQLQuery(hDB, osSQL.c_str());
    2917         272 :     if (!oResult || oResult->RowCount() == 0)
    2918             :     {
    2919         108 :         if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
    2920         108 :             pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
    2921             :             pszContentsMaxY != nullptr)
    2922             :         {
    2923          53 :             osSQL = pszSQL;
    2924          53 :             osSQL += " ORDER BY zoom_level DESC";
    2925          53 :             if (!GetUpdate())
    2926          27 :                 osSQL += " LIMIT 1";
    2927          53 :             oResult = SQLQuery(hDB, osSQL.c_str());
    2928             :         }
    2929          54 :         if (!oResult || oResult->RowCount() == 0)
    2930             :         {
    2931           1 :             if (oResult && pszZoomLevel != nullptr)
    2932             :             {
    2933           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2934             :                          "ZOOM_LEVEL is probably not valid w.r.t tile "
    2935             :                          "table content");
    2936             :             }
    2937           1 :             sqlite3_free(pszSQL);
    2938           1 :             return false;
    2939             :         }
    2940             :     }
    2941         271 :     sqlite3_free(pszSQL);
    2942             : 
    2943             :     // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
    2944             :     // actually exist.
    2945             : 
    2946             :     // CAUTION: Do not move those variables inside inner scope !
    2947         542 :     CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
    2948             : 
    2949         271 :     if (CPLTestBool(
    2950             :             CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
    2951             :     {
    2952          13 :         pszSQL = sqlite3_mprintf(
    2953             :             "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
    2954             :             "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
    2955             :             pszTableName, atoi(oResult->GetValue(0, 0)));
    2956          13 :         auto oResult2 = SQLQuery(hDB, pszSQL);
    2957          13 :         sqlite3_free(pszSQL);
    2958          26 :         if (!oResult2 || oResult2->RowCount() == 0 ||
    2959             :             // Can happen if table is empty
    2960          38 :             oResult2->GetValue(0, 0) == nullptr ||
    2961             :             // Can happen if table has no NOT NULL constraint on tile_row
    2962             :             // and that all tile_row are NULL
    2963          12 :             oResult2->GetValue(1, 0) == nullptr)
    2964             :         {
    2965           1 :             return false;
    2966             :         }
    2967          12 :         const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
    2968          12 :         const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
    2969          12 :         const int nTileWidth = atoi(oResult->GetValue(3, 0));
    2970          12 :         const int nTileHeight = atoi(oResult->GetValue(4, 0));
    2971             :         osContentsMinX =
    2972          24 :             CPLSPrintf("%.17g", dfMinX + dfPixelXSize * nTileWidth *
    2973          12 :                                              atoi(oResult2->GetValue(0, 0)));
    2974             :         osContentsMaxY =
    2975          24 :             CPLSPrintf("%.17g", dfMaxY - dfPixelYSize * nTileHeight *
    2976          12 :                                              atoi(oResult2->GetValue(1, 0)));
    2977             :         osContentsMaxX = CPLSPrintf(
    2978          24 :             "%.17g", dfMinX + dfPixelXSize * nTileWidth *
    2979          12 :                                   (1 + atoi(oResult2->GetValue(2, 0))));
    2980             :         osContentsMinY = CPLSPrintf(
    2981          24 :             "%.17g", dfMaxY - dfPixelYSize * nTileHeight *
    2982          12 :                                   (1 + atoi(oResult2->GetValue(3, 0))));
    2983          12 :         pszContentsMinX = osContentsMinX.c_str();
    2984          12 :         pszContentsMinY = osContentsMinY.c_str();
    2985          12 :         pszContentsMaxX = osContentsMaxX.c_str();
    2986          12 :         pszContentsMaxY = osContentsMaxY.c_str();
    2987             :     }
    2988             : 
    2989         270 :     if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
    2990             :                     pszContentsMinX, pszContentsMinY, pszContentsMaxX,
    2991         270 :                     pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
    2992             :     {
    2993           3 :         return false;
    2994             :     }
    2995             : 
    2996             :     auto poBand =
    2997         267 :         reinterpret_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
    2998         267 :     if (!osDataNull.empty())
    2999             :     {
    3000          23 :         double dfGPKGNoDataValue = CPLAtof(osDataNull);
    3001          23 :         if (m_eTF == GPKG_TF_PNG_16BIT)
    3002             :         {
    3003          21 :             if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
    3004          21 :                 static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
    3005             :             {
    3006           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    3007             :                          "data_null = %.17g is invalid for integer data_type",
    3008             :                          dfGPKGNoDataValue);
    3009             :             }
    3010             :             else
    3011             :             {
    3012          21 :                 m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
    3013          21 :                 if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
    3014           9 :                     dfGPKGNoDataValue = -32768.0;
    3015          12 :                 else if (m_eDT == GDT_Float32)
    3016             :                 {
    3017             :                     // Pick a value that is unlikely to be hit with offset &
    3018             :                     // scale
    3019           4 :                     dfGPKGNoDataValue = -std::numeric_limits<float>::max();
    3020             :                 }
    3021          21 :                 poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
    3022             :             }
    3023             :         }
    3024             :         else
    3025             :         {
    3026           2 :             poBand->SetNoDataValueInternal(
    3027           2 :                 static_cast<float>(dfGPKGNoDataValue));
    3028             :         }
    3029             :     }
    3030         267 :     if (!osUom.empty())
    3031             :     {
    3032           2 :         poBand->SetUnitTypeInternal(osUom);
    3033             :     }
    3034         267 :     if (!osFieldName.empty())
    3035             :     {
    3036          64 :         GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
    3037             :     }
    3038         267 :     if (!osGridCellEncoding.empty())
    3039             :     {
    3040          64 :         if (osGridCellEncoding == "grid-value-is-center")
    3041             :         {
    3042          15 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    3043             :                                             GDALMD_AOP_POINT);
    3044             :         }
    3045          49 :         else if (osGridCellEncoding == "grid-value-is-area")
    3046             :         {
    3047          45 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    3048             :                                             GDALMD_AOP_AREA);
    3049             :         }
    3050             :         else
    3051             :         {
    3052           4 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    3053             :                                             GDALMD_AOP_POINT);
    3054           4 :             GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
    3055             :                 "GRID_CELL_ENCODING", osGridCellEncoding);
    3056             :         }
    3057             :     }
    3058             : 
    3059         267 :     CheckUnknownExtensions(true);
    3060             : 
    3061             :     // Do this after CheckUnknownExtensions() so that m_eTF is set to
    3062             :     // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
    3063         267 :     const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
    3064         267 :     if (pszTF)
    3065             :     {
    3066           4 :         if (!GetUpdate())
    3067             :         {
    3068           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    3069             :                      "TILE_FORMAT open option ignored in read-only mode");
    3070             :         }
    3071           4 :         else if (m_eTF == GPKG_TF_PNG_16BIT ||
    3072           4 :                  m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
    3073             :         {
    3074           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    3075             :                      "TILE_FORMAT open option ignored on gridded coverages");
    3076             :         }
    3077             :         else
    3078             :         {
    3079           4 :             GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
    3080           4 :             if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
    3081             :             {
    3082           1 :                 if (!RegisterWebPExtension())
    3083           0 :                     return false;
    3084             :             }
    3085           4 :             m_eTF = eTF;
    3086             :         }
    3087             :     }
    3088             : 
    3089         267 :     ParseCompressionOptions(papszOpenOptionsIn);
    3090             : 
    3091         267 :     m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
    3092             : 
    3093             :     // Set metadata
    3094         267 :     if (pszIdentifier && pszIdentifier[0])
    3095         267 :         GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
    3096         267 :     if (pszDescription && pszDescription[0])
    3097          21 :         GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
    3098             : 
    3099             :     // Add overviews
    3100         352 :     for (int i = 1; i < oResult->RowCount(); i++)
    3101             :     {
    3102          86 :         GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
    3103          86 :         poOvrDS->ShareLockWithParentDataset(this);
    3104          86 :         if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
    3105             :                                  dfMaxY, pszContentsMinX, pszContentsMinY,
    3106             :                                  pszContentsMaxX, pszContentsMaxY,
    3107          86 :                                  papszOpenOptionsIn, *oResult, i))
    3108             :         {
    3109           0 :             delete poOvrDS;
    3110           1 :             break;
    3111             :         }
    3112             : 
    3113          86 :         m_papoOverviewDS = static_cast<GDALGeoPackageDataset **>(
    3114         172 :             CPLRealloc(m_papoOverviewDS, sizeof(GDALGeoPackageDataset *) *
    3115          86 :                                              (m_nOverviewCount + 1)));
    3116          86 :         m_papoOverviewDS[m_nOverviewCount++] = poOvrDS;
    3117             : 
    3118             :         int nTileWidth, nTileHeight;
    3119          86 :         poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3120          87 :         if (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
    3121           1 :             poOvrDS->GetRasterYSize() < nTileHeight)
    3122             :         {
    3123           1 :             break;
    3124             :         }
    3125             :     }
    3126             : 
    3127         267 :     return true;
    3128             : }
    3129             : 
    3130             : /************************************************************************/
    3131             : /*                           GetSpatialRef()                            */
    3132             : /************************************************************************/
    3133             : 
    3134          14 : const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
    3135             : {
    3136          14 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3137             : }
    3138             : 
    3139             : /************************************************************************/
    3140             : /*                           SetSpatialRef()                            */
    3141             : /************************************************************************/
    3142             : 
    3143         147 : CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    3144             : {
    3145         147 :     if (nBands == 0)
    3146             :     {
    3147           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3148             :                  "SetProjection() not supported on a dataset with 0 band");
    3149           1 :         return CE_Failure;
    3150             :     }
    3151         146 :     if (eAccess != GA_Update)
    3152             :     {
    3153           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3154             :                  "SetProjection() not supported on read-only dataset");
    3155           1 :         return CE_Failure;
    3156             :     }
    3157             : 
    3158         145 :     const int nSRID = GetSrsId(poSRS);
    3159         290 :     const auto poTS = GetTilingScheme(m_osTilingScheme);
    3160         145 :     if (poTS && nSRID != poTS->nEPSGCode)
    3161             :     {
    3162           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    3163             :                  "Projection should be EPSG:%d for %s tiling scheme",
    3164           1 :                  poTS->nEPSGCode, m_osTilingScheme.c_str());
    3165           1 :         return CE_Failure;
    3166             :     }
    3167             : 
    3168         144 :     m_nSRID = nSRID;
    3169         144 :     m_oSRS.Clear();
    3170         144 :     if (poSRS)
    3171         143 :         m_oSRS = *poSRS;
    3172             : 
    3173         144 :     if (m_bRecordInsertedInGPKGContent)
    3174             :     {
    3175         119 :         char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
    3176             :                                        "WHERE lower(table_name) = lower('%q')",
    3177             :                                        m_nSRID, m_osRasterTable.c_str());
    3178         119 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
    3179         119 :         sqlite3_free(pszSQL);
    3180         119 :         if (eErr != OGRERR_NONE)
    3181           0 :             return CE_Failure;
    3182             : 
    3183         119 :         pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
    3184             :                                  "WHERE lower(table_name) = lower('%q')",
    3185             :                                  m_nSRID, m_osRasterTable.c_str());
    3186         119 :         eErr = SQLCommand(hDB, pszSQL);
    3187         119 :         sqlite3_free(pszSQL);
    3188         119 :         if (eErr != OGRERR_NONE)
    3189           0 :             return CE_Failure;
    3190             :     }
    3191             : 
    3192         144 :     return CE_None;
    3193             : }
    3194             : 
    3195             : /************************************************************************/
    3196             : /*                          GetGeoTransform()                           */
    3197             : /************************************************************************/
    3198             : 
    3199          33 : CPLErr GDALGeoPackageDataset::GetGeoTransform(double *padfGeoTransform)
    3200             : {
    3201          33 :     memcpy(padfGeoTransform, m_adfGeoTransform, 6 * sizeof(double));
    3202          33 :     if (!m_bGeoTransformValid)
    3203           2 :         return CE_Failure;
    3204             :     else
    3205          31 :         return CE_None;
    3206             : }
    3207             : 
    3208             : /************************************************************************/
    3209             : /*                          SetGeoTransform()                           */
    3210             : /************************************************************************/
    3211             : 
    3212         186 : CPLErr GDALGeoPackageDataset::SetGeoTransform(double *padfGeoTransform)
    3213             : {
    3214         186 :     if (nBands == 0)
    3215             :     {
    3216           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    3217             :                  "SetGeoTransform() not supported on a dataset with 0 band");
    3218           2 :         return CE_Failure;
    3219             :     }
    3220         184 :     if (eAccess != GA_Update)
    3221             :     {
    3222           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3223             :                  "SetGeoTransform() not supported on read-only dataset");
    3224           1 :         return CE_Failure;
    3225             :     }
    3226         183 :     if (m_bGeoTransformValid)
    3227             :     {
    3228           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3229             :                  "Cannot modify geotransform once set");
    3230           1 :         return CE_Failure;
    3231             :     }
    3232         182 :     if (padfGeoTransform[2] != 0.0 || padfGeoTransform[4] != 0 ||
    3233         182 :         padfGeoTransform[5] > 0.0)
    3234             :     {
    3235           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3236             :                  "Only north-up non rotated geotransform supported");
    3237           0 :         return CE_Failure;
    3238             :     }
    3239             : 
    3240         182 :     if (m_nZoomLevel < 0)
    3241             :     {
    3242         181 :         const auto poTS = GetTilingScheme(m_osTilingScheme);
    3243         181 :         if (poTS)
    3244             :         {
    3245          20 :             double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
    3246          20 :             double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
    3247         199 :             for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
    3248         179 :                  m_nZoomLevel++)
    3249             :             {
    3250         198 :                 double dfExpectedPixelXSize =
    3251         198 :                     dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
    3252         198 :                 double dfExpectedPixelYSize =
    3253         198 :                     dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
    3254         198 :                 if (fabs(padfGeoTransform[1] - dfExpectedPixelXSize) <
    3255         198 :                         1e-8 * dfExpectedPixelXSize &&
    3256          19 :                     fabs(fabs(padfGeoTransform[5]) - dfExpectedPixelYSize) <
    3257          19 :                         1e-8 * dfExpectedPixelYSize)
    3258             :                 {
    3259          19 :                     break;
    3260             :                 }
    3261             :             }
    3262          20 :             if (m_nZoomLevel == MAX_ZOOM_LEVEL)
    3263             :             {
    3264           1 :                 m_nZoomLevel = -1;
    3265           1 :                 CPLError(
    3266             :                     CE_Failure, CPLE_NotSupported,
    3267             :                     "Could not find an appropriate zoom level of %s tiling "
    3268             :                     "scheme that matches raster pixel size",
    3269             :                     m_osTilingScheme.c_str());
    3270           1 :                 return CE_Failure;
    3271             :             }
    3272             :         }
    3273             :     }
    3274             : 
    3275         181 :     memcpy(m_adfGeoTransform, padfGeoTransform, 6 * sizeof(double));
    3276         181 :     m_bGeoTransformValid = true;
    3277             : 
    3278         181 :     return FinalizeRasterRegistration();
    3279             : }
    3280             : 
    3281             : /************************************************************************/
    3282             : /*                      FinalizeRasterRegistration()                    */
    3283             : /************************************************************************/
    3284             : 
    3285         181 : CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
    3286             : {
    3287             :     OGRErr eErr;
    3288             : 
    3289         181 :     m_dfTMSMinX = m_adfGeoTransform[0];
    3290         181 :     m_dfTMSMaxY = m_adfGeoTransform[3];
    3291             : 
    3292             :     int nTileWidth, nTileHeight;
    3293         181 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3294             : 
    3295         181 :     if (m_nZoomLevel < 0)
    3296             :     {
    3297         161 :         m_nZoomLevel = 0;
    3298         235 :         while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
    3299         161 :                (nRasterYSize >> m_nZoomLevel) > nTileHeight)
    3300          74 :             m_nZoomLevel++;
    3301             :     }
    3302             : 
    3303         181 :     double dfPixelXSizeZoomLevel0 = m_adfGeoTransform[1] * (1 << m_nZoomLevel);
    3304         181 :     double dfPixelYSizeZoomLevel0 =
    3305         181 :         fabs(m_adfGeoTransform[5]) * (1 << m_nZoomLevel);
    3306             :     int nTileXCountZoomLevel0 =
    3307         181 :         std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
    3308             :     int nTileYCountZoomLevel0 =
    3309         181 :         std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
    3310             : 
    3311         362 :     const auto poTS = GetTilingScheme(m_osTilingScheme);
    3312         181 :     if (poTS)
    3313             :     {
    3314          20 :         CPLAssert(m_nZoomLevel >= 0);
    3315          20 :         m_dfTMSMinX = poTS->dfMinX;
    3316          20 :         m_dfTMSMaxY = poTS->dfMaxY;
    3317          20 :         dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
    3318          20 :         dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
    3319          20 :         nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
    3320          20 :         nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
    3321             :     }
    3322         181 :     m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
    3323         181 :     m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
    3324             : 
    3325         181 :     if (!ComputeTileAndPixelShifts())
    3326             :     {
    3327           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3328             :                  "Overflow occurred in ComputeTileAndPixelShifts()");
    3329           0 :         return CE_Failure;
    3330             :     }
    3331             : 
    3332         181 :     if (!AllocCachedTiles())
    3333             :     {
    3334           0 :         return CE_Failure;
    3335             :     }
    3336             : 
    3337         181 :     double dfGDALMinX = m_adfGeoTransform[0];
    3338         181 :     double dfGDALMinY =
    3339         181 :         m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
    3340         181 :     double dfGDALMaxX =
    3341         181 :         m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
    3342         181 :     double dfGDALMaxY = m_adfGeoTransform[3];
    3343             : 
    3344         181 :     if (SoftStartTransaction() != OGRERR_NONE)
    3345           0 :         return CE_Failure;
    3346             : 
    3347             :     const char *pszCurrentDate =
    3348         181 :         CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
    3349             :     CPLString osInsertGpkgContentsFormatting(
    3350             :         "INSERT INTO gpkg_contents "
    3351             :         "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
    3352             :         "last_change,srs_id) VALUES "
    3353         362 :         "('%q','%q','%q','%q',%.17g,%.17g,%.17g,%.17g,");
    3354         181 :     osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
    3355         181 :     osInsertGpkgContentsFormatting += ",%d)";
    3356         362 :     char *pszSQL = sqlite3_mprintf(
    3357             :         osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
    3358         181 :         (m_eDT == GDT_Byte) ? "tiles" : "2d-gridded-coverage",
    3359             :         m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
    3360             :         dfGDALMaxX, dfGDALMaxY,
    3361             :         pszCurrentDate ? pszCurrentDate
    3362             :                        : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
    3363             :         m_nSRID);
    3364             : 
    3365         181 :     eErr = SQLCommand(hDB, pszSQL);
    3366         181 :     sqlite3_free(pszSQL);
    3367         181 :     if (eErr != OGRERR_NONE)
    3368             :     {
    3369           8 :         SoftRollbackTransaction();
    3370           8 :         return CE_Failure;
    3371             :     }
    3372             : 
    3373         173 :     double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
    3374             :                                          dfPixelXSizeZoomLevel0;
    3375         173 :     double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
    3376             :                                          dfPixelYSizeZoomLevel0;
    3377             : 
    3378             :     pszSQL =
    3379         173 :         sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
    3380             :                         "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
    3381             :                         "('%q',%d,%.17g,%.17g,%.17g,%.17g)",
    3382             :                         m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
    3383             :                         dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
    3384         173 :     eErr = SQLCommand(hDB, pszSQL);
    3385         173 :     sqlite3_free(pszSQL);
    3386         173 :     if (eErr != OGRERR_NONE)
    3387             :     {
    3388           0 :         SoftRollbackTransaction();
    3389           0 :         return CE_Failure;
    3390             :     }
    3391             : 
    3392         173 :     m_papoOverviewDS = static_cast<GDALGeoPackageDataset **>(
    3393         173 :         CPLCalloc(sizeof(GDALGeoPackageDataset *), m_nZoomLevel));
    3394             : 
    3395         581 :     for (int i = 0; i <= m_nZoomLevel; i++)
    3396             :     {
    3397         408 :         double dfPixelXSizeZoomLevel = 0.0;
    3398         408 :         double dfPixelYSizeZoomLevel = 0.0;
    3399         408 :         int nTileMatrixWidth = 0;
    3400         408 :         int nTileMatrixHeight = 0;
    3401         408 :         if (EQUAL(m_osTilingScheme, "CUSTOM"))
    3402             :         {
    3403         227 :             dfPixelXSizeZoomLevel =
    3404         227 :                 m_adfGeoTransform[1] * (1 << (m_nZoomLevel - i));
    3405         227 :             dfPixelYSizeZoomLevel =
    3406         227 :                 fabs(m_adfGeoTransform[5]) * (1 << (m_nZoomLevel - i));
    3407             :         }
    3408             :         else
    3409             :         {
    3410         181 :             dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
    3411         181 :             dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
    3412             :         }
    3413         408 :         nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
    3414         408 :         nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
    3415             : 
    3416         408 :         pszSQL = sqlite3_mprintf(
    3417             :             "INSERT INTO gpkg_tile_matrix "
    3418             :             "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
    3419             :             "height,pixel_x_size,pixel_y_size) VALUES "
    3420             :             "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
    3421             :             m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
    3422             :             nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
    3423             :             dfPixelYSizeZoomLevel);
    3424         408 :         eErr = SQLCommand(hDB, pszSQL);
    3425         408 :         sqlite3_free(pszSQL);
    3426         408 :         if (eErr != OGRERR_NONE)
    3427             :         {
    3428           0 :             SoftRollbackTransaction();
    3429           0 :             return CE_Failure;
    3430             :         }
    3431             : 
    3432         408 :         if (i < m_nZoomLevel)
    3433             :         {
    3434         235 :             GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
    3435         235 :             poOvrDS->ShareLockWithParentDataset(this);
    3436         235 :             poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
    3437             :                                 m_dfTMSMaxY, dfPixelXSizeZoomLevel,
    3438             :                                 dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
    3439             :                                 nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
    3440             :                                 dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
    3441             : 
    3442         235 :             m_papoOverviewDS[m_nZoomLevel - 1 - i] = poOvrDS;
    3443             :         }
    3444             :     }
    3445             : 
    3446         173 :     if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
    3447             :     {
    3448          40 :         eErr = SQLCommand(
    3449             :             hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
    3450          40 :         m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
    3451          40 :         if (eErr != OGRERR_NONE)
    3452             :         {
    3453           0 :             SoftRollbackTransaction();
    3454           0 :             return CE_Failure;
    3455             :         }
    3456             :     }
    3457             : 
    3458         173 :     SoftCommitTransaction();
    3459             : 
    3460         173 :     m_nOverviewCount = m_nZoomLevel;
    3461         173 :     m_bRecordInsertedInGPKGContent = true;
    3462             : 
    3463         173 :     return CE_None;
    3464             : }
    3465             : 
    3466             : /************************************************************************/
    3467             : /*                             FlushCache()                             */
    3468             : /************************************************************************/
    3469             : 
    3470        2522 : CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
    3471             : {
    3472        2522 :     if (m_bInFlushCache)
    3473           0 :         return CE_None;
    3474             : 
    3475        2522 :     if (eAccess == GA_Update || !m_bMetadataDirty)
    3476             :     {
    3477        2519 :         SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    3478             :     }
    3479             : 
    3480        2522 :     if (m_bRemoveOGREmptyTable)
    3481             :     {
    3482         628 :         m_bRemoveOGREmptyTable = false;
    3483         628 :         RemoveOGREmptyTable();
    3484             :     }
    3485             : 
    3486        2522 :     CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
    3487             : 
    3488        2522 :     FlushMetadata();
    3489             : 
    3490        2522 :     if (eAccess == GA_Update || !m_bMetadataDirty)
    3491             :     {
    3492             :         // Needed again as above IFlushCacheWithErrCode()
    3493             :         // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
    3494             :         // which modifies metadata
    3495        2522 :         SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    3496             :     }
    3497             : 
    3498        2522 :     return eErr;
    3499             : }
    3500             : 
    3501        4742 : CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
    3502             : 
    3503             : {
    3504        4742 :     if (m_bInFlushCache)
    3505        2153 :         return CE_None;
    3506        2589 :     m_bInFlushCache = true;
    3507        2589 :     if (hDB && eAccess == GA_ReadOnly && bAtClosing)
    3508             :     {
    3509             :         // Clean-up metadata that will go to PAM by removing items that
    3510             :         // are reconstructed.
    3511        1906 :         CPLStringList aosMD;
    3512        1572 :         for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
    3513             :              ++papszIter)
    3514             :         {
    3515         619 :             char *pszKey = nullptr;
    3516         619 :             CPLParseNameValue(*papszIter, &pszKey);
    3517        1238 :             if (pszKey &&
    3518         619 :                 (EQUAL(pszKey, "AREA_OR_POINT") ||
    3519         473 :                  EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
    3520         254 :                  EQUAL(pszKey, "ZOOM_LEVEL") ||
    3521         649 :                  STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
    3522             :             {
    3523             :                 // remove it
    3524             :             }
    3525             :             else
    3526             :             {
    3527          30 :                 aosMD.AddString(*papszIter);
    3528             :             }
    3529         619 :             CPLFree(pszKey);
    3530             :         }
    3531         953 :         oMDMD.SetMetadata(aosMD.List());
    3532         953 :         oMDMD.SetMetadata(nullptr, "IMAGE_STRUCTURE");
    3533             : 
    3534        1906 :         GDALPamDataset::FlushCache(bAtClosing);
    3535             :     }
    3536             :     else
    3537             :     {
    3538             :         // Short circuit GDALPamDataset to avoid serialization to .aux.xml
    3539        1636 :         GDALDataset::FlushCache(bAtClosing);
    3540             :     }
    3541             : 
    3542        6435 :     for (int i = 0; i < m_nLayers; i++)
    3543             :     {
    3544        3846 :         m_papoLayers[i]->RunDeferredCreationIfNecessary();
    3545        3846 :         m_papoLayers[i]->CreateSpatialIndexIfNecessary();
    3546             :     }
    3547             : 
    3548             :     // Update raster table last_change column in gpkg_contents if needed
    3549        2589 :     if (m_bHasModifiedTiles)
    3550             :     {
    3551         536 :         for (int i = 1; i <= nBands; ++i)
    3552             :         {
    3553             :             auto poBand =
    3554         357 :                 cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
    3555         357 :             if (!poBand->HaveStatsMetadataBeenSetInThisSession())
    3556             :             {
    3557         344 :                 poBand->InvalidateStatistics();
    3558         344 :                 if (psPam && psPam->pszPamFilename)
    3559         344 :                     VSIUnlink(psPam->pszPamFilename);
    3560             :             }
    3561             :         }
    3562             : 
    3563         179 :         UpdateGpkgContentsLastChange(m_osRasterTable);
    3564             : 
    3565         179 :         m_bHasModifiedTiles = false;
    3566             :     }
    3567             : 
    3568        2589 :     CPLErr eErr = FlushTiles();
    3569             : 
    3570        2589 :     m_bInFlushCache = false;
    3571        2589 :     return eErr;
    3572             : }
    3573             : 
    3574             : /************************************************************************/
    3575             : /*                       GetCurrentDateEscapedSQL()                      */
    3576             : /************************************************************************/
    3577             : 
    3578        1837 : std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
    3579             : {
    3580             :     const char *pszCurrentDate =
    3581        1837 :         CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
    3582        1837 :     if (pszCurrentDate)
    3583           6 :         return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
    3584        1834 :     return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
    3585             : }
    3586             : 
    3587             : /************************************************************************/
    3588             : /*                    UpdateGpkgContentsLastChange()                    */
    3589             : /************************************************************************/
    3590             : 
    3591             : OGRErr
    3592         797 : GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
    3593             : {
    3594             :     char *pszSQL =
    3595         797 :         sqlite3_mprintf("UPDATE gpkg_contents SET "
    3596             :                         "last_change = %s "
    3597             :                         "WHERE lower(table_name) = lower('%q')",
    3598        1594 :                         GetCurrentDateEscapedSQL().c_str(), pszTableName);
    3599         797 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
    3600         797 :     sqlite3_free(pszSQL);
    3601         797 :     return eErr;
    3602             : }
    3603             : 
    3604             : /************************************************************************/
    3605             : /*                          IBuildOverviews()                           */
    3606             : /************************************************************************/
    3607             : 
    3608          20 : CPLErr GDALGeoPackageDataset::IBuildOverviews(
    3609             :     const char *pszResampling, int nOverviews, const int *panOverviewList,
    3610             :     int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
    3611             :     void *pProgressData, CSLConstList papszOptions)
    3612             : {
    3613          20 :     if (GetAccess() != GA_Update)
    3614             :     {
    3615           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3616             :                  "Overview building not supported on a database opened in "
    3617             :                  "read-only mode");
    3618           1 :         return CE_Failure;
    3619             :     }
    3620          19 :     if (m_poParentDS != nullptr)
    3621             :     {
    3622           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3623             :                  "Overview building not supported on overview dataset");
    3624           1 :         return CE_Failure;
    3625             :     }
    3626             : 
    3627          18 :     if (nOverviews == 0)
    3628             :     {
    3629           5 :         for (int i = 0; i < m_nOverviewCount; i++)
    3630           3 :             m_papoOverviewDS[i]->FlushCache(false);
    3631             : 
    3632           2 :         SoftStartTransaction();
    3633             : 
    3634           2 :         if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
    3635             :         {
    3636           1 :             char *pszSQL = sqlite3_mprintf(
    3637             :                 "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
    3638             :                 "(SELECT y.id FROM \"%w\" x "
    3639             :                 "JOIN gpkg_2d_gridded_tile_ancillary y "
    3640             :                 "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
    3641             :                 "x.zoom_level < %d)",
    3642             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
    3643           1 :             OGRErr eErr = SQLCommand(hDB, pszSQL);
    3644           1 :             sqlite3_free(pszSQL);
    3645           1 :             if (eErr != OGRERR_NONE)
    3646             :             {
    3647           0 :                 SoftRollbackTransaction();
    3648           0 :                 return CE_Failure;
    3649             :             }
    3650             :         }
    3651             : 
    3652             :         char *pszSQL =
    3653           2 :             sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
    3654             :                             m_osRasterTable.c_str(), m_nZoomLevel);
    3655           2 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
    3656           2 :         sqlite3_free(pszSQL);
    3657           2 :         if (eErr != OGRERR_NONE)
    3658             :         {
    3659           0 :             SoftRollbackTransaction();
    3660           0 :             return CE_Failure;
    3661             :         }
    3662             : 
    3663           2 :         SoftCommitTransaction();
    3664             : 
    3665           2 :         return CE_None;
    3666             :     }
    3667             : 
    3668          16 :     if (nBandsIn != nBands)
    3669             :     {
    3670           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3671             :                  "Generation of overviews in GPKG only"
    3672             :                  "supported when operating on all bands.");
    3673           0 :         return CE_Failure;
    3674             :     }
    3675             : 
    3676          16 :     if (m_nOverviewCount == 0)
    3677             :     {
    3678           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3679             :                  "Image too small to support overviews");
    3680           0 :         return CE_Failure;
    3681             :     }
    3682             : 
    3683          16 :     FlushCache(false);
    3684          60 :     for (int i = 0; i < nOverviews; i++)
    3685             :     {
    3686          47 :         if (panOverviewList[i] < 2)
    3687             :         {
    3688           1 :             CPLError(CE_Failure, CPLE_IllegalArg,
    3689             :                      "Overview factor must be >= 2");
    3690           1 :             return CE_Failure;
    3691             :         }
    3692             : 
    3693          46 :         bool bFound = false;
    3694          46 :         int jCandidate = -1;
    3695          46 :         int nMaxOvFactor = 0;
    3696         196 :         for (int j = 0; j < m_nOverviewCount; j++)
    3697             :         {
    3698         190 :             auto poODS = m_papoOverviewDS[j];
    3699         190 :             const int nOvFactor = static_cast<int>(
    3700         190 :                 0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
    3701             : 
    3702         190 :             nMaxOvFactor = nOvFactor;
    3703             : 
    3704         190 :             if (nOvFactor == panOverviewList[i])
    3705             :             {
    3706          40 :                 bFound = true;
    3707          40 :                 break;
    3708             :             }
    3709             : 
    3710         150 :             if (jCandidate < 0 && nOvFactor > panOverviewList[i])
    3711           1 :                 jCandidate = j;
    3712             :         }
    3713             : 
    3714          46 :         if (!bFound)
    3715             :         {
    3716             :             /* Mostly for debug */
    3717           6 :             if (!CPLTestBool(CPLGetConfigOption(
    3718             :                     "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
    3719             :             {
    3720           2 :                 CPLString osOvrList;
    3721           4 :                 for (int j = 0; j < m_nOverviewCount; j++)
    3722             :                 {
    3723           2 :                     auto poODS = m_papoOverviewDS[j];
    3724           2 :                     const int nOvFactor =
    3725           2 :                         static_cast<int>(0.5 + poODS->m_adfGeoTransform[1] /
    3726           2 :                                                    m_adfGeoTransform[1]);
    3727             : 
    3728           2 :                     if (j != 0)
    3729           0 :                         osOvrList += " ";
    3730           2 :                     osOvrList += CPLSPrintf("%d", nOvFactor);
    3731             :                 }
    3732           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    3733             :                          "Only overviews %s can be computed",
    3734             :                          osOvrList.c_str());
    3735           2 :                 return CE_Failure;
    3736             :             }
    3737             :             else
    3738             :             {
    3739           4 :                 int nOvFactor = panOverviewList[i];
    3740           4 :                 if (jCandidate < 0)
    3741           3 :                     jCandidate = m_nOverviewCount;
    3742             : 
    3743           4 :                 int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
    3744           4 :                 int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
    3745           4 :                 if (!(jCandidate == m_nOverviewCount &&
    3746           3 :                       nOvFactor == 2 * nMaxOvFactor) &&
    3747           1 :                     !m_bZoomOther)
    3748             :                 {
    3749           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    3750             :                              "Use of overview factor %d causes gpkg_zoom_other "
    3751             :                              "extension to be needed",
    3752             :                              nOvFactor);
    3753           1 :                     RegisterZoomOtherExtension();
    3754           1 :                     m_bZoomOther = true;
    3755             :                 }
    3756             : 
    3757           4 :                 SoftStartTransaction();
    3758             : 
    3759           4 :                 CPLAssert(jCandidate > 0);
    3760           4 :                 int nNewZoomLevel =
    3761           4 :                     m_papoOverviewDS[jCandidate - 1]->m_nZoomLevel;
    3762             : 
    3763             :                 char *pszSQL;
    3764             :                 OGRErr eErr;
    3765          24 :                 for (int k = 0; k <= jCandidate; k++)
    3766             :                 {
    3767          60 :                     pszSQL = sqlite3_mprintf(
    3768             :                         "UPDATE gpkg_tile_matrix SET zoom_level = %d "
    3769             :                         "WHERE lower(table_name) = lower('%q') AND zoom_level "
    3770             :                         "= %d",
    3771          20 :                         m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
    3772          20 :                         m_nZoomLevel - k);
    3773          20 :                     eErr = SQLCommand(hDB, pszSQL);
    3774          20 :                     sqlite3_free(pszSQL);
    3775          20 :                     if (eErr != OGRERR_NONE)
    3776             :                     {
    3777           0 :                         SoftRollbackTransaction();
    3778           0 :                         return CE_Failure;
    3779             :                     }
    3780             : 
    3781             :                     pszSQL =
    3782          20 :                         sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
    3783             :                                         "WHERE zoom_level = %d",
    3784             :                                         m_osRasterTable.c_str(),
    3785          20 :                                         m_nZoomLevel - k + 1, m_nZoomLevel - k);
    3786          20 :                     eErr = SQLCommand(hDB, pszSQL);
    3787          20 :                     sqlite3_free(pszSQL);
    3788          20 :                     if (eErr != OGRERR_NONE)
    3789             :                     {
    3790           0 :                         SoftRollbackTransaction();
    3791           0 :                         return CE_Failure;
    3792             :                     }
    3793             :                 }
    3794             : 
    3795           4 :                 double dfGDALMinX = m_adfGeoTransform[0];
    3796           4 :                 double dfGDALMinY =
    3797           4 :                     m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
    3798           4 :                 double dfGDALMaxX =
    3799           4 :                     m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
    3800           4 :                 double dfGDALMaxY = m_adfGeoTransform[3];
    3801           4 :                 double dfPixelXSizeZoomLevel = m_adfGeoTransform[1] * nOvFactor;
    3802           4 :                 double dfPixelYSizeZoomLevel =
    3803           4 :                     fabs(m_adfGeoTransform[5]) * nOvFactor;
    3804             :                 int nTileWidth, nTileHeight;
    3805           4 :                 GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3806           4 :                 int nTileMatrixWidth = (nOvXSize + nTileWidth - 1) / nTileWidth;
    3807           4 :                 int nTileMatrixHeight =
    3808           4 :                     (nOvYSize + nTileHeight - 1) / nTileHeight;
    3809           4 :                 pszSQL = sqlite3_mprintf(
    3810             :                     "INSERT INTO gpkg_tile_matrix "
    3811             :                     "(table_name,zoom_level,matrix_width,matrix_height,tile_"
    3812             :                     "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
    3813             :                     "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
    3814             :                     m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
    3815             :                     nTileMatrixHeight, nTileWidth, nTileHeight,
    3816             :                     dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
    3817           4 :                 eErr = SQLCommand(hDB, pszSQL);
    3818           4 :                 sqlite3_free(pszSQL);
    3819           4 :                 if (eErr != OGRERR_NONE)
    3820             :                 {
    3821           0 :                     SoftRollbackTransaction();
    3822           0 :                     return CE_Failure;
    3823             :                 }
    3824             : 
    3825           4 :                 SoftCommitTransaction();
    3826             : 
    3827           4 :                 m_nZoomLevel++; /* this change our zoom level as well as
    3828             :                                    previous overviews */
    3829          20 :                 for (int k = 0; k < jCandidate; k++)
    3830          16 :                     m_papoOverviewDS[k]->m_nZoomLevel++;
    3831             : 
    3832           4 :                 GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
    3833           4 :                 poOvrDS->ShareLockWithParentDataset(this);
    3834           4 :                 poOvrDS->InitRaster(
    3835             :                     this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
    3836             :                     m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
    3837             :                     nTileWidth, nTileHeight, nTileMatrixWidth,
    3838             :                     nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
    3839             :                     dfGDALMaxY);
    3840           4 :                 m_papoOverviewDS =
    3841           8 :                     static_cast<GDALGeoPackageDataset **>(CPLRealloc(
    3842           4 :                         m_papoOverviewDS, sizeof(GDALGeoPackageDataset *) *
    3843           4 :                                               (m_nOverviewCount + 1)));
    3844             : 
    3845           4 :                 if (jCandidate < m_nOverviewCount)
    3846             :                 {
    3847           1 :                     memmove(m_papoOverviewDS + jCandidate + 1,
    3848           1 :                             m_papoOverviewDS + jCandidate,
    3849             :                             sizeof(GDALGeoPackageDataset *) *
    3850           1 :                                 (m_nOverviewCount - jCandidate));
    3851             :                 }
    3852           4 :                 m_papoOverviewDS[jCandidate] = poOvrDS;
    3853           4 :                 m_nOverviewCount++;
    3854             :             }
    3855             :         }
    3856             :     }
    3857             : 
    3858             :     GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
    3859          13 :         CPLCalloc(sizeof(GDALRasterBand **), nBands));
    3860          13 :     CPLErr eErr = CE_None;
    3861          49 :     for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
    3862             :     {
    3863          72 :         papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
    3864          36 :             CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
    3865          36 :         int iCurOverview = 0;
    3866         185 :         for (int i = 0; i < nOverviews; i++)
    3867             :         {
    3868         149 :             int j = 0;  // Used after for.
    3869         724 :             for (; j < m_nOverviewCount; j++)
    3870             :             {
    3871         724 :                 auto poODS = m_papoOverviewDS[j];
    3872         724 :                 const int nOvFactor = static_cast<int>(
    3873         724 :                     0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
    3874             : 
    3875         724 :                 if (nOvFactor == panOverviewList[i])
    3876             :                 {
    3877         298 :                     papapoOverviewBands[iBand][iCurOverview] =
    3878         149 :                         poODS->GetRasterBand(iBand + 1);
    3879         149 :                     iCurOverview++;
    3880         149 :                     break;
    3881             :                 }
    3882             :             }
    3883         149 :             if (j == m_nOverviewCount)
    3884             :             {
    3885           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3886             :                          "Could not find dataset corresponding to ov factor %d",
    3887           0 :                          panOverviewList[i]);
    3888           0 :                 eErr = CE_Failure;
    3889             :             }
    3890             :         }
    3891          36 :         if (eErr == CE_None)
    3892             :         {
    3893          36 :             CPLAssert(iCurOverview == nOverviews);
    3894             :         }
    3895             :     }
    3896             : 
    3897          13 :     if (eErr == CE_None)
    3898          13 :         eErr = GDALRegenerateOverviewsMultiBand(
    3899          13 :             nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
    3900             :             pfnProgress, pProgressData, papszOptions);
    3901             : 
    3902          49 :     for (int iBand = 0; iBand < nBands; iBand++)
    3903             :     {
    3904          36 :         CPLFree(papapoOverviewBands[iBand]);
    3905             :     }
    3906          13 :     CPLFree(papapoOverviewBands);
    3907             : 
    3908          13 :     return eErr;
    3909             : }
    3910             : 
    3911             : /************************************************************************/
    3912             : /*                            GetFileList()                             */
    3913             : /************************************************************************/
    3914             : 
    3915          37 : char **GDALGeoPackageDataset::GetFileList()
    3916             : {
    3917          37 :     TryLoadXML();
    3918          37 :     return GDALPamDataset::GetFileList();
    3919             : }
    3920             : 
    3921             : /************************************************************************/
    3922             : /*                      GetMetadataDomainList()                         */
    3923             : /************************************************************************/
    3924             : 
    3925          41 : char **GDALGeoPackageDataset::GetMetadataDomainList()
    3926             : {
    3927          41 :     GetMetadata();
    3928          41 :     if (!m_osRasterTable.empty())
    3929           5 :         GetMetadata("GEOPACKAGE");
    3930          41 :     return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
    3931          41 :                                    TRUE, "SUBDATASETS", nullptr);
    3932             : }
    3933             : 
    3934             : /************************************************************************/
    3935             : /*                        CheckMetadataDomain()                         */
    3936             : /************************************************************************/
    3937             : 
    3938        5079 : const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
    3939             : {
    3940        5262 :     if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
    3941         183 :         m_osRasterTable.empty())
    3942             :     {
    3943           4 :         CPLError(
    3944             :             CE_Warning, CPLE_IllegalArg,
    3945             :             "Using GEOPACKAGE for a non-raster geopackage is not supported. "
    3946             :             "Using default domain instead");
    3947           4 :         return nullptr;
    3948             :     }
    3949        5075 :     return pszDomain;
    3950             : }
    3951             : 
    3952             : /************************************************************************/
    3953             : /*                           HasMetadataTables()                        */
    3954             : /************************************************************************/
    3955             : 
    3956        5141 : bool GDALGeoPackageDataset::HasMetadataTables() const
    3957             : {
    3958        5141 :     if (m_nHasMetadataTables < 0)
    3959             :     {
    3960             :         const int nCount =
    3961        1918 :             SQLGetInteger(hDB,
    3962             :                           "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
    3963             :                           "('gpkg_metadata', 'gpkg_metadata_reference') "
    3964             :                           "AND type IN ('table', 'view')",
    3965             :                           nullptr);
    3966        1918 :         m_nHasMetadataTables = nCount == 2;
    3967             :     }
    3968        5141 :     return CPL_TO_BOOL(m_nHasMetadataTables);
    3969             : }
    3970             : 
    3971             : /************************************************************************/
    3972             : /*                         HasDataColumnsTable()                        */
    3973             : /************************************************************************/
    3974             : 
    3975        1144 : bool GDALGeoPackageDataset::HasDataColumnsTable() const
    3976             : {
    3977        2288 :     const int nCount = SQLGetInteger(
    3978        1144 :         hDB,
    3979             :         "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
    3980             :         "AND type IN ('table', 'view')",
    3981             :         nullptr);
    3982        1144 :     return nCount == 1;
    3983             : }
    3984             : 
    3985             : /************************************************************************/
    3986             : /*                    HasDataColumnConstraintsTable()                   */
    3987             : /************************************************************************/
    3988             : 
    3989         119 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
    3990             : {
    3991         119 :     const int nCount = SQLGetInteger(hDB,
    3992             :                                      "SELECT 1 FROM sqlite_master WHERE name = "
    3993             :                                      "'gpkg_data_column_constraints'"
    3994             :                                      "AND type IN ('table', 'view')",
    3995             :                                      nullptr);
    3996         119 :     return nCount == 1;
    3997             : }
    3998             : 
    3999             : /************************************************************************/
    4000             : /*                  HasDataColumnConstraintsTableGPKG_1_0()             */
    4001             : /************************************************************************/
    4002             : 
    4003          73 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
    4004             : {
    4005          73 :     if (m_nApplicationId != GP10_APPLICATION_ID)
    4006          71 :         return false;
    4007             :     // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
    4008             :     // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
    4009           2 :     bool bRet = false;
    4010           2 :     sqlite3_stmt *hSQLStmt = nullptr;
    4011           2 :     int rc = sqlite3_prepare_v2(hDB,
    4012             :                                 "SELECT minIsInclusive, maxIsInclusive FROM "
    4013             :                                 "gpkg_data_column_constraints",
    4014             :                                 -1, &hSQLStmt, nullptr);
    4015           2 :     if (rc == SQLITE_OK)
    4016             :     {
    4017           2 :         bRet = true;
    4018           2 :         sqlite3_finalize(hSQLStmt);
    4019             :     }
    4020           2 :     return bRet;
    4021             : }
    4022             : 
    4023             : /************************************************************************/
    4024             : /*      CreateColumnsTableAndColumnConstraintsTablesIfNecessary()       */
    4025             : /************************************************************************/
    4026             : 
    4027          49 : bool GDALGeoPackageDataset::
    4028             :     CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
    4029             : {
    4030          49 :     if (!HasDataColumnsTable())
    4031             :     {
    4032             :         // Geopackage < 1.3 had
    4033             :         // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
    4034             :         // gpkg_contents(table_name) instead of the unique constraint.
    4035          10 :         if (OGRERR_NONE !=
    4036          10 :             SQLCommand(
    4037             :                 GetDB(),
    4038             :                 "CREATE TABLE gpkg_data_columns ("
    4039             :                 "table_name TEXT NOT NULL,"
    4040             :                 "column_name TEXT NOT NULL,"
    4041             :                 "name TEXT,"
    4042             :                 "title TEXT,"
    4043             :                 "description TEXT,"
    4044             :                 "mime_type TEXT,"
    4045             :                 "constraint_name TEXT,"
    4046             :                 "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
    4047             :                 "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
    4048             :         {
    4049           0 :             return false;
    4050             :         }
    4051             :     }
    4052          49 :     if (!HasDataColumnConstraintsTable())
    4053             :     {
    4054          22 :         const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
    4055          11 :                                            ? "min_is_inclusive"
    4056             :                                            : "minIsInclusive";
    4057          22 :         const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
    4058          11 :                                            ? "max_is_inclusive"
    4059             :                                            : "maxIsInclusive";
    4060             : 
    4061             :         const std::string osSQL(
    4062             :             CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
    4063             :                        "constraint_name TEXT NOT NULL,"
    4064             :                        "constraint_type TEXT NOT NULL,"
    4065             :                        "value TEXT,"
    4066             :                        "min NUMERIC,"
    4067             :                        "%s BOOLEAN,"
    4068             :                        "max NUMERIC,"
    4069             :                        "%s BOOLEAN,"
    4070             :                        "description TEXT,"
    4071             :                        "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
    4072             :                        "constraint_type, value));",
    4073          11 :                        min_is_inclusive, max_is_inclusive));
    4074          11 :         if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
    4075             :         {
    4076           0 :             return false;
    4077             :         }
    4078             :     }
    4079          49 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    4080             :     {
    4081           0 :         return false;
    4082             :     }
    4083          49 :     if (SQLGetInteger(GetDB(),
    4084             :                       "SELECT 1 FROM gpkg_extensions WHERE "
    4085             :                       "table_name = 'gpkg_data_columns'",
    4086          49 :                       nullptr) != 1)
    4087             :     {
    4088          11 :         if (OGRERR_NONE !=
    4089          11 :             SQLCommand(
    4090             :                 GetDB(),
    4091             :                 "INSERT INTO gpkg_extensions "
    4092             :                 "(table_name,column_name,extension_name,definition,scope) "
    4093             :                 "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
    4094             :                 "'http://www.geopackage.org/spec121/#extension_schema', "
    4095             :                 "'read-write')"))
    4096             :         {
    4097           0 :             return false;
    4098             :         }
    4099             :     }
    4100          49 :     if (SQLGetInteger(GetDB(),
    4101             :                       "SELECT 1 FROM gpkg_extensions WHERE "
    4102             :                       "table_name = 'gpkg_data_column_constraints'",
    4103          49 :                       nullptr) != 1)
    4104             :     {
    4105          11 :         if (OGRERR_NONE !=
    4106          11 :             SQLCommand(
    4107             :                 GetDB(),
    4108             :                 "INSERT INTO gpkg_extensions "
    4109             :                 "(table_name,column_name,extension_name,definition,scope) "
    4110             :                 "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
    4111             :                 "'http://www.geopackage.org/spec121/#extension_schema', "
    4112             :                 "'read-write')"))
    4113             :         {
    4114           0 :             return false;
    4115             :         }
    4116             :     }
    4117             : 
    4118          49 :     return true;
    4119             : }
    4120             : 
    4121             : /************************************************************************/
    4122             : /*                        HasGpkgextRelationsTable()                    */
    4123             : /************************************************************************/
    4124             : 
    4125        1129 : bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
    4126             : {
    4127        2258 :     const int nCount = SQLGetInteger(
    4128        1129 :         hDB,
    4129             :         "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
    4130             :         "AND type IN ('table', 'view')",
    4131             :         nullptr);
    4132        1129 :     return nCount == 1;
    4133             : }
    4134             : 
    4135             : /************************************************************************/
    4136             : /*                    CreateRelationsTableIfNecessary()                 */
    4137             : /************************************************************************/
    4138             : 
    4139           9 : bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
    4140             : {
    4141           9 :     if (HasGpkgextRelationsTable())
    4142             :     {
    4143           5 :         return true;
    4144             :     }
    4145             : 
    4146           4 :     if (OGRERR_NONE !=
    4147           4 :         SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
    4148             :                             "id INTEGER PRIMARY KEY AUTOINCREMENT,"
    4149             :                             "base_table_name TEXT NOT NULL,"
    4150             :                             "base_primary_column TEXT NOT NULL DEFAULT 'id',"
    4151             :                             "related_table_name TEXT NOT NULL,"
    4152             :                             "related_primary_column TEXT NOT NULL DEFAULT 'id',"
    4153             :                             "relation_name TEXT NOT NULL,"
    4154             :                             "mapping_table_name TEXT NOT NULL UNIQUE);"))
    4155             :     {
    4156           0 :         return false;
    4157             :     }
    4158             : 
    4159           4 :     return true;
    4160             : }
    4161             : 
    4162             : /************************************************************************/
    4163             : /*                        HasQGISLayerStyles()                          */
    4164             : /************************************************************************/
    4165             : 
    4166          11 : bool GDALGeoPackageDataset::HasQGISLayerStyles() const
    4167             : {
    4168             :     // QGIS layer_styles extension:
    4169             :     // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
    4170          11 :     bool bRet = false;
    4171             :     const int nCount =
    4172          11 :         SQLGetInteger(hDB,
    4173             :                       "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
    4174             :                       "AND type = 'table'",
    4175             :                       nullptr);
    4176          11 :     if (nCount == 1)
    4177             :     {
    4178           1 :         sqlite3_stmt *hSQLStmt = nullptr;
    4179           2 :         int rc = sqlite3_prepare_v2(
    4180           1 :             hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
    4181             :             &hSQLStmt, nullptr);
    4182           1 :         if (rc == SQLITE_OK)
    4183             :         {
    4184           1 :             bRet = true;
    4185           1 :             sqlite3_finalize(hSQLStmt);
    4186             :         }
    4187             :     }
    4188          11 :     return bRet;
    4189             : }
    4190             : 
    4191             : /************************************************************************/
    4192             : /*                            GetMetadata()                             */
    4193             : /************************************************************************/
    4194             : 
    4195        3410 : char **GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
    4196             : 
    4197             : {
    4198        3410 :     pszDomain = CheckMetadataDomain(pszDomain);
    4199        3410 :     if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
    4200          60 :         return m_aosSubDatasets.List();
    4201             : 
    4202        3350 :     if (m_bHasReadMetadataFromStorage)
    4203        1484 :         return GDALPamDataset::GetMetadata(pszDomain);
    4204             : 
    4205        1866 :     m_bHasReadMetadataFromStorage = true;
    4206             : 
    4207        1866 :     TryLoadXML();
    4208             : 
    4209        1866 :     if (!HasMetadataTables())
    4210        1379 :         return GDALPamDataset::GetMetadata(pszDomain);
    4211             : 
    4212         487 :     char *pszSQL = nullptr;
    4213         487 :     if (!m_osRasterTable.empty())
    4214             :     {
    4215         170 :         pszSQL = sqlite3_mprintf(
    4216             :             "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
    4217             :             "mdr.reference_scope FROM gpkg_metadata md "
    4218             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4219             :             "WHERE "
    4220             :             "(mdr.reference_scope = 'geopackage' OR "
    4221             :             "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
    4222             :             "lower('%q'))) ORDER BY md.id "
    4223             :             "LIMIT 1000",  // to avoid denial of service
    4224             :             m_osRasterTable.c_str());
    4225             :     }
    4226             :     else
    4227             :     {
    4228         317 :         pszSQL = sqlite3_mprintf(
    4229             :             "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
    4230             :             "mdr.reference_scope FROM gpkg_metadata md "
    4231             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4232             :             "WHERE "
    4233             :             "mdr.reference_scope = 'geopackage' ORDER BY md.id "
    4234             :             "LIMIT 1000"  // to avoid denial of service
    4235             :         );
    4236             :     }
    4237             : 
    4238         974 :     auto oResult = SQLQuery(hDB, pszSQL);
    4239         487 :     sqlite3_free(pszSQL);
    4240         487 :     if (!oResult)
    4241             :     {
    4242           0 :         return GDALPamDataset::GetMetadata(pszDomain);
    4243             :     }
    4244             : 
    4245         487 :     char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
    4246             : 
    4247             :     /* GDAL metadata */
    4248         677 :     for (int i = 0; i < oResult->RowCount(); i++)
    4249             :     {
    4250         190 :         const char *pszMetadata = oResult->GetValue(0, i);
    4251         190 :         const char *pszMDStandardURI = oResult->GetValue(1, i);
    4252         190 :         const char *pszMimeType = oResult->GetValue(2, i);
    4253         190 :         const char *pszReferenceScope = oResult->GetValue(3, i);
    4254         190 :         if (pszMetadata && pszMDStandardURI && pszMimeType &&
    4255         190 :             pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
    4256         174 :             EQUAL(pszMimeType, "text/xml"))
    4257             :         {
    4258         174 :             CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
    4259         174 :             if (psXMLNode)
    4260             :             {
    4261         348 :                 GDALMultiDomainMetadata oLocalMDMD;
    4262         174 :                 oLocalMDMD.XMLInit(psXMLNode, FALSE);
    4263         333 :                 if (!m_osRasterTable.empty() &&
    4264         159 :                     EQUAL(pszReferenceScope, "geopackage"))
    4265             :                 {
    4266           6 :                     oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
    4267             :                 }
    4268             :                 else
    4269             :                 {
    4270             :                     papszMetadata =
    4271         168 :                         CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
    4272         168 :                     CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
    4273         168 :                     CSLConstList papszIter = papszDomainList;
    4274         447 :                     while (papszIter && *papszIter)
    4275             :                     {
    4276         279 :                         if (EQUAL(*papszIter, "IMAGE_STRUCTURE"))
    4277             :                         {
    4278             :                             CSLConstList papszMD =
    4279         126 :                                 oLocalMDMD.GetMetadata(*papszIter);
    4280             :                             const char *pszBAND_COUNT =
    4281         126 :                                 CSLFetchNameValue(papszMD, "BAND_COUNT");
    4282         126 :                             if (pszBAND_COUNT)
    4283         124 :                                 m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
    4284             : 
    4285             :                             const char *pszCOLOR_TABLE =
    4286         126 :                                 CSLFetchNameValue(papszMD, "COLOR_TABLE");
    4287         126 :                             if (pszCOLOR_TABLE)
    4288             :                             {
    4289             :                                 const CPLStringList aosTokens(
    4290             :                                     CSLTokenizeString2(pszCOLOR_TABLE, "{,",
    4291          26 :                                                        0));
    4292          13 :                                 if ((aosTokens.size() % 4) == 0)
    4293             :                                 {
    4294          13 :                                     const int nColors = aosTokens.size() / 4;
    4295             :                                     m_poCTFromMetadata =
    4296          13 :                                         std::make_unique<GDALColorTable>();
    4297        3341 :                                     for (int iColor = 0; iColor < nColors;
    4298             :                                          ++iColor)
    4299             :                                     {
    4300             :                                         GDALColorEntry sEntry;
    4301        3328 :                                         sEntry.c1 = static_cast<short>(
    4302        3328 :                                             atoi(aosTokens[4 * iColor + 0]));
    4303        3328 :                                         sEntry.c2 = static_cast<short>(
    4304        3328 :                                             atoi(aosTokens[4 * iColor + 1]));
    4305        3328 :                                         sEntry.c3 = static_cast<short>(
    4306        3328 :                                             atoi(aosTokens[4 * iColor + 2]));
    4307        3328 :                                         sEntry.c4 = static_cast<short>(
    4308        3328 :                                             atoi(aosTokens[4 * iColor + 3]));
    4309        3328 :                                         m_poCTFromMetadata->SetColorEntry(
    4310             :                                             iColor, &sEntry);
    4311             :                                     }
    4312             :                                 }
    4313             :                             }
    4314             : 
    4315             :                             const char *pszTILE_FORMAT =
    4316         126 :                                 CSLFetchNameValue(papszMD, "TILE_FORMAT");
    4317         126 :                             if (pszTILE_FORMAT)
    4318             :                             {
    4319           8 :                                 m_osTFFromMetadata = pszTILE_FORMAT;
    4320           8 :                                 oMDMD.SetMetadataItem("TILE_FORMAT",
    4321             :                                                       pszTILE_FORMAT,
    4322             :                                                       "IMAGE_STRUCTURE");
    4323             :                             }
    4324             : 
    4325             :                             const char *pszNodataValue =
    4326         126 :                                 CSLFetchNameValue(papszMD, "NODATA_VALUE");
    4327         126 :                             if (pszNodataValue)
    4328             :                             {
    4329           2 :                                 m_osNodataValueFromMetadata = pszNodataValue;
    4330             :                             }
    4331             :                         }
    4332             : 
    4333         153 :                         else if (!EQUAL(*papszIter, "") &&
    4334          16 :                                  !STARTS_WITH(*papszIter, "BAND_"))
    4335             :                         {
    4336          12 :                             oMDMD.SetMetadata(
    4337           6 :                                 oLocalMDMD.GetMetadata(*papszIter), *papszIter);
    4338             :                         }
    4339         279 :                         papszIter++;
    4340             :                     }
    4341             :                 }
    4342         174 :                 CPLDestroyXMLNode(psXMLNode);
    4343             :             }
    4344             :         }
    4345             :     }
    4346             : 
    4347         487 :     GDALPamDataset::SetMetadata(papszMetadata);
    4348         487 :     CSLDestroy(papszMetadata);
    4349         487 :     papszMetadata = nullptr;
    4350             : 
    4351             :     /* Add non-GDAL metadata now */
    4352         487 :     int nNonGDALMDILocal = 1;
    4353         487 :     int nNonGDALMDIGeopackage = 1;
    4354         677 :     for (int i = 0; i < oResult->RowCount(); i++)
    4355             :     {
    4356         190 :         const char *pszMetadata = oResult->GetValue(0, i);
    4357         190 :         const char *pszMDStandardURI = oResult->GetValue(1, i);
    4358         190 :         const char *pszMimeType = oResult->GetValue(2, i);
    4359         190 :         const char *pszReferenceScope = oResult->GetValue(3, i);
    4360         190 :         if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
    4361         190 :             pszMimeType == nullptr || pszReferenceScope == nullptr)
    4362             :         {
    4363             :             // should not happen as there are NOT NULL constraints
    4364             :             // But a database could lack such NOT NULL constraints or have
    4365             :             // large values that would cause a memory allocation failure.
    4366           0 :             continue;
    4367             :         }
    4368         190 :         int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
    4369         190 :         if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
    4370         174 :             EQUAL(pszMimeType, "text/xml"))
    4371         174 :             continue;
    4372             : 
    4373          16 :         if (!m_osRasterTable.empty() && bIsGPKGScope)
    4374             :         {
    4375           8 :             oMDMD.SetMetadataItem(
    4376             :                 CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
    4377             :                 pszMetadata, "GEOPACKAGE");
    4378           8 :             nNonGDALMDIGeopackage++;
    4379             :         }
    4380             :         /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
    4381             :         ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
    4382             :         {
    4383             :             char* apszMD[2];
    4384             :             apszMD[0] = (char*)pszMetadata;
    4385             :             apszMD[1] = NULL;
    4386             :             oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
    4387             :         }*/
    4388             :         else
    4389             :         {
    4390           8 :             oMDMD.SetMetadataItem(
    4391             :                 CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
    4392             :                 pszMetadata);
    4393           8 :             nNonGDALMDILocal++;
    4394             :         }
    4395             :     }
    4396             : 
    4397         487 :     return GDALPamDataset::GetMetadata(pszDomain);
    4398             : }
    4399             : 
    4400             : /************************************************************************/
    4401             : /*                            WriteMetadata()                           */
    4402             : /************************************************************************/
    4403             : 
    4404         694 : void GDALGeoPackageDataset::WriteMetadata(
    4405             :     CPLXMLNode *psXMLNode, /* will be destroyed by the method */
    4406             :     const char *pszTableName)
    4407             : {
    4408         694 :     const bool bIsEmpty = (psXMLNode == nullptr);
    4409         694 :     if (!HasMetadataTables())
    4410             :     {
    4411         502 :         if (bIsEmpty || !CreateMetadataTables())
    4412             :         {
    4413         229 :             CPLDestroyXMLNode(psXMLNode);
    4414         229 :             return;
    4415             :         }
    4416             :     }
    4417             : 
    4418         465 :     char *pszXML = nullptr;
    4419         465 :     if (!bIsEmpty)
    4420             :     {
    4421             :         CPLXMLNode *psMasterXMLNode =
    4422         316 :             CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
    4423         316 :         psMasterXMLNode->psChild = psXMLNode;
    4424         316 :         pszXML = CPLSerializeXMLTree(psMasterXMLNode);
    4425         316 :         CPLDestroyXMLNode(psMasterXMLNode);
    4426             :     }
    4427             :     // cppcheck-suppress uselessAssignmentPtrArg
    4428         465 :     psXMLNode = nullptr;
    4429             : 
    4430         465 :     char *pszSQL = nullptr;
    4431         465 :     if (pszTableName && pszTableName[0] != '\0')
    4432             :     {
    4433         324 :         pszSQL = sqlite3_mprintf(
    4434             :             "SELECT md.id FROM gpkg_metadata md "
    4435             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4436             :             "WHERE md.md_scope = 'dataset' AND "
    4437             :             "md.md_standard_uri='http://gdal.org' "
    4438             :             "AND md.mime_type='text/xml' AND mdr.reference_scope = 'table' AND "
    4439             :             "lower(mdr.table_name) = lower('%q')",
    4440             :             pszTableName);
    4441             :     }
    4442             :     else
    4443             :     {
    4444         141 :         pszSQL = sqlite3_mprintf(
    4445             :             "SELECT md.id FROM gpkg_metadata md "
    4446             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4447             :             "WHERE md.md_scope = 'dataset' AND "
    4448             :             "md.md_standard_uri='http://gdal.org' "
    4449             :             "AND md.mime_type='text/xml' AND mdr.reference_scope = "
    4450             :             "'geopackage'");
    4451             :     }
    4452             :     OGRErr err;
    4453         465 :     int mdId = SQLGetInteger(hDB, pszSQL, &err);
    4454         465 :     if (err != OGRERR_NONE)
    4455         435 :         mdId = -1;
    4456         465 :     sqlite3_free(pszSQL);
    4457             : 
    4458         465 :     if (bIsEmpty)
    4459             :     {
    4460         149 :         if (mdId >= 0)
    4461             :         {
    4462           6 :             SQLCommand(
    4463             :                 hDB,
    4464             :                 CPLSPrintf(
    4465             :                     "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
    4466             :                     mdId));
    4467           6 :             SQLCommand(
    4468             :                 hDB,
    4469             :                 CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
    4470             :         }
    4471             :     }
    4472             :     else
    4473             :     {
    4474         316 :         if (mdId >= 0)
    4475             :         {
    4476          24 :             pszSQL = sqlite3_mprintf(
    4477             :                 "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
    4478             :                 pszXML, mdId);
    4479             :         }
    4480             :         else
    4481             :         {
    4482             :             pszSQL =
    4483         292 :                 sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
    4484             :                                 "md_standard_uri, mime_type, metadata) VALUES "
    4485             :                                 "('dataset','http://gdal.org','text/xml','%q')",
    4486             :                                 pszXML);
    4487             :         }
    4488         316 :         SQLCommand(hDB, pszSQL);
    4489         316 :         sqlite3_free(pszSQL);
    4490             : 
    4491         316 :         CPLFree(pszXML);
    4492             : 
    4493         316 :         if (mdId < 0)
    4494             :         {
    4495         292 :             const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
    4496         292 :             if (pszTableName != nullptr && pszTableName[0] != '\0')
    4497             :             {
    4498         280 :                 pszSQL = sqlite3_mprintf(
    4499             :                     "INSERT INTO gpkg_metadata_reference (reference_scope, "
    4500             :                     "table_name, timestamp, md_file_id) VALUES "
    4501             :                     "('table', '%q', %s, %d)",
    4502         560 :                     pszTableName, GetCurrentDateEscapedSQL().c_str(),
    4503             :                     static_cast<int>(nFID));
    4504             :             }
    4505             :             else
    4506             :             {
    4507          12 :                 pszSQL = sqlite3_mprintf(
    4508             :                     "INSERT INTO gpkg_metadata_reference (reference_scope, "
    4509             :                     "timestamp, md_file_id) VALUES "
    4510             :                     "('geopackage', %s, %d)",
    4511          24 :                     GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
    4512             :             }
    4513             :         }
    4514             :         else
    4515             :         {
    4516          24 :             pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
    4517             :                                      "timestamp = %s WHERE md_file_id = %d",
    4518          48 :                                      GetCurrentDateEscapedSQL().c_str(), mdId);
    4519             :         }
    4520         316 :         SQLCommand(hDB, pszSQL);
    4521         316 :         sqlite3_free(pszSQL);
    4522             :     }
    4523             : }
    4524             : 
    4525             : /************************************************************************/
    4526             : /*                        CreateMetadataTables()                        */
    4527             : /************************************************************************/
    4528             : 
    4529         288 : bool GDALGeoPackageDataset::CreateMetadataTables()
    4530             : {
    4531             :     const bool bCreateTriggers =
    4532         288 :         CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
    4533             : 
    4534             :     /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL  */
    4535             :     CPLString osSQL = "CREATE TABLE gpkg_metadata ("
    4536             :                       "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
    4537             :                       "md_scope TEXT NOT NULL DEFAULT 'dataset',"
    4538             :                       "md_standard_uri TEXT NOT NULL,"
    4539             :                       "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
    4540             :                       "metadata TEXT NOT NULL DEFAULT ''"
    4541         576 :                       ")";
    4542             : 
    4543             :     /* From D.2. metadata Table 40. metadata Trigger Definition SQL  */
    4544         288 :     const char *pszMetadataTriggers =
    4545             :         "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
    4546             :         "BEFORE INSERT ON 'gpkg_metadata' "
    4547             :         "FOR EACH ROW BEGIN "
    4548             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
    4549             :         "constraint: md_scope must be one of undefined | fieldSession | "
    4550             :         "collectionSession | series | dataset | featureType | feature | "
    4551             :         "attributeType | attribute | tile | model | catalogue | schema | "
    4552             :         "taxonomy software | service | collectionHardware | "
    4553             :         "nonGeographicDataset | dimensionGroup') "
    4554             :         "WHERE NOT(NEW.md_scope IN "
    4555             :         "('undefined','fieldSession','collectionSession','series','dataset', "
    4556             :         "'featureType','feature','attributeType','attribute','tile','model', "
    4557             :         "'catalogue','schema','taxonomy','software','service', "
    4558             :         "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
    4559             :         "END; "
    4560             :         "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
    4561             :         "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
    4562             :         "FOR EACH ROW BEGIN "
    4563             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
    4564             :         "constraint: md_scope must be one of undefined | fieldSession | "
    4565             :         "collectionSession | series | dataset | featureType | feature | "
    4566             :         "attributeType | attribute | tile | model | catalogue | schema | "
    4567             :         "taxonomy software | service | collectionHardware | "
    4568             :         "nonGeographicDataset | dimensionGroup') "
    4569             :         "WHERE NOT(NEW.md_scope IN "
    4570             :         "('undefined','fieldSession','collectionSession','series','dataset', "
    4571             :         "'featureType','feature','attributeType','attribute','tile','model', "
    4572             :         "'catalogue','schema','taxonomy','software','service', "
    4573             :         "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
    4574             :         "END";
    4575         288 :     if (bCreateTriggers)
    4576             :     {
    4577           0 :         osSQL += ";";
    4578           0 :         osSQL += pszMetadataTriggers;
    4579             :     }
    4580             : 
    4581             :     /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
    4582             :      * Table Definition SQL */
    4583             :     osSQL += ";"
    4584             :              "CREATE TABLE gpkg_metadata_reference ("
    4585             :              "reference_scope TEXT NOT NULL,"
    4586             :              "table_name TEXT,"
    4587             :              "column_name TEXT,"
    4588             :              "row_id_value INTEGER,"
    4589             :              "timestamp DATETIME NOT NULL DEFAULT "
    4590             :              "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
    4591             :              "md_file_id INTEGER NOT NULL,"
    4592             :              "md_parent_id INTEGER,"
    4593             :              "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
    4594             :              "gpkg_metadata(id),"
    4595             :              "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
    4596             :              "gpkg_metadata(id)"
    4597         288 :              ")";
    4598             : 
    4599             :     /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
    4600             :      * Definition SQL   */
    4601         288 :     const char *pszMetadataReferenceTriggers =
    4602             :         "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_insert' "
    4603             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4604             :         "FOR EACH ROW BEGIN "
    4605             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4606             :         "violates constraint: reference_scope must be one of \"geopackage\", "
    4607             :         "table\", \"column\", \"row\", \"row/col\"') "
    4608             :         "WHERE NOT NEW.reference_scope IN "
    4609             :         "('geopackage','table','column','row','row/col'); "
    4610             :         "END; "
    4611             :         "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
    4612             :         "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
    4613             :         "FOR EACH ROW BEGIN "
    4614             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4615             :         "violates constraint: reference_scope must be one of \"geopackage\", "
    4616             :         "\"table\", \"column\", \"row\", \"row/col\"') "
    4617             :         "WHERE NOT NEW.reference_scope IN "
    4618             :         "('geopackage','table','column','row','row/col'); "
    4619             :         "END; "
    4620             :         "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
    4621             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4622             :         "FOR EACH ROW BEGIN "
    4623             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4624             :         "violates constraint: column name must be NULL when reference_scope "
    4625             :         "is \"geopackage\", \"table\" or \"row\"') "
    4626             :         "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
    4627             :         "AND NEW.column_name IS NOT NULL); "
    4628             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4629             :         "violates constraint: column name must be defined for the specified "
    4630             :         "table when reference_scope is \"column\" or \"row/col\"') "
    4631             :         "WHERE (NEW.reference_scope IN ('column','row/col') "
    4632             :         "AND NOT NEW.table_name IN ( "
    4633             :         "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
    4634             :         "AND name = NEW.table_name "
    4635             :         "AND sql LIKE ('%' || NEW.column_name || '%'))); "
    4636             :         "END; "
    4637             :         "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
    4638             :         "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
    4639             :         "FOR EACH ROW BEGIN "
    4640             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4641             :         "violates constraint: column name must be NULL when reference_scope "
    4642             :         "is \"geopackage\", \"table\" or \"row\"') "
    4643             :         "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
    4644             :         "AND NEW.column_name IS NOT NULL); "
    4645             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4646             :         "violates constraint: column name must be defined for the specified "
    4647             :         "table when reference_scope is \"column\" or \"row/col\"') "
    4648             :         "WHERE (NEW.reference_scope IN ('column','row/col') "
    4649             :         "AND NOT NEW.table_name IN ( "
    4650             :         "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
    4651             :         "AND name = NEW.table_name "
    4652             :         "AND sql LIKE ('%' || NEW.column_name || '%'))); "
    4653             :         "END; "
    4654             :         "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_insert' "
    4655             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4656             :         "FOR EACH ROW BEGIN "
    4657             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4658             :         "violates constraint: row_id_value must be NULL when reference_scope "
    4659             :         "is \"geopackage\", \"table\" or \"column\"') "
    4660             :         "WHERE NEW.reference_scope IN ('geopackage','table','column') "
    4661             :         "AND NEW.row_id_value IS NOT NULL; "
    4662             :         "END; "
    4663             :         "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
    4664             :         "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
    4665             :         "FOR EACH ROW BEGIN "
    4666             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4667             :         "violates constraint: row_id_value must be NULL when reference_scope "
    4668             :         "is \"geopackage\", \"table\" or \"column\"') "
    4669             :         "WHERE NEW.reference_scope IN ('geopackage','table','column') "
    4670             :         "AND NEW.row_id_value IS NOT NULL; "
    4671             :         "END; "
    4672             :         "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
    4673             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4674             :         "FOR EACH ROW BEGIN "
    4675             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4676             :         "violates constraint: timestamp must be a valid time in ISO 8601 "
    4677             :         "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
    4678             :         "WHERE NOT (NEW.timestamp GLOB "
    4679             :         "'[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-"
    4680             :         "5][0-9].[0-9][0-9][0-9]Z' "
    4681             :         "AND strftime('%s',NEW.timestamp) NOT NULL); "
    4682             :         "END; "
    4683             :         "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
    4684             :         "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
    4685             :         "FOR EACH ROW BEGIN "
    4686             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4687             :         "violates constraint: timestamp must be a valid time in ISO 8601 "
    4688             :         "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
    4689             :         "WHERE NOT (NEW.timestamp GLOB "
    4690             :         "'[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-"
    4691             :         "5][0-9].[0-9][0-9][0-9]Z' "
    4692             :         "AND strftime('%s',NEW.timestamp) NOT NULL); "
    4693             :         "END";
    4694         288 :     if (bCreateTriggers)
    4695             :     {
    4696           0 :         osSQL += ";";
    4697           0 :         osSQL += pszMetadataReferenceTriggers;
    4698             :     }
    4699             : 
    4700         288 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    4701           2 :         return false;
    4702             : 
    4703         286 :     osSQL += ";";
    4704             :     osSQL += "INSERT INTO gpkg_extensions "
    4705             :              "(table_name, column_name, extension_name, definition, scope) "
    4706             :              "VALUES "
    4707             :              "('gpkg_metadata', NULL, 'gpkg_metadata', "
    4708             :              "'http://www.geopackage.org/spec120/#extension_metadata', "
    4709         286 :              "'read-write')";
    4710             : 
    4711         286 :     osSQL += ";";
    4712             :     osSQL += "INSERT INTO gpkg_extensions "
    4713             :              "(table_name, column_name, extension_name, definition, scope) "
    4714             :              "VALUES "
    4715             :              "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
    4716             :              "'http://www.geopackage.org/spec120/#extension_metadata', "
    4717         286 :              "'read-write')";
    4718             : 
    4719         286 :     const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
    4720         286 :     m_nHasMetadataTables = bOK;
    4721         286 :     return bOK;
    4722             : }
    4723             : 
    4724             : /************************************************************************/
    4725             : /*                            FlushMetadata()                           */
    4726             : /************************************************************************/
    4727             : 
    4728        8367 : void GDALGeoPackageDataset::FlushMetadata()
    4729             : {
    4730        8367 :     if (!m_bMetadataDirty || m_poParentDS != nullptr ||
    4731         349 :         m_nCreateMetadataTables == FALSE)
    4732        8024 :         return;
    4733         343 :     m_bMetadataDirty = false;
    4734             : 
    4735         343 :     if (eAccess == GA_ReadOnly)
    4736             :     {
    4737           3 :         return;
    4738             :     }
    4739             : 
    4740         340 :     bool bCanWriteAreaOrPoint =
    4741         678 :         !m_bGridCellEncodingAsCO &&
    4742         338 :         (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
    4743         340 :     if (!m_osRasterTable.empty())
    4744             :     {
    4745             :         const char *pszIdentifier =
    4746         142 :             GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
    4747             :         const char *pszDescription =
    4748         142 :             GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
    4749         171 :         if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
    4750          29 :             pszIdentifier != m_osIdentifier)
    4751             :         {
    4752          14 :             m_osIdentifier = pszIdentifier;
    4753             :             char *pszSQL =
    4754          14 :                 sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
    4755             :                                 "WHERE lower(table_name) = lower('%q')",
    4756             :                                 pszIdentifier, m_osRasterTable.c_str());
    4757          14 :             SQLCommand(hDB, pszSQL);
    4758          14 :             sqlite3_free(pszSQL);
    4759             :         }
    4760         149 :         if (!m_bDescriptionAsCO && pszDescription != nullptr &&
    4761           7 :             pszDescription != m_osDescription)
    4762             :         {
    4763           7 :             m_osDescription = pszDescription;
    4764             :             char *pszSQL =
    4765           7 :                 sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
    4766             :                                 "WHERE lower(table_name) = lower('%q')",
    4767             :                                 pszDescription, m_osRasterTable.c_str());
    4768           7 :             SQLCommand(hDB, pszSQL);
    4769           7 :             sqlite3_free(pszSQL);
    4770             :         }
    4771         142 :         if (bCanWriteAreaOrPoint)
    4772             :         {
    4773             :             const char *pszAreaOrPoint =
    4774          28 :                 GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
    4775          28 :             if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
    4776             :             {
    4777          23 :                 bCanWriteAreaOrPoint = false;
    4778          23 :                 char *pszSQL = sqlite3_mprintf(
    4779             :                     "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
    4780             :                     "grid_cell_encoding = 'grid-value-is-area' WHERE "
    4781             :                     "lower(tile_matrix_set_name) = lower('%q')",
    4782             :                     m_osRasterTable.c_str());
    4783          23 :                 SQLCommand(hDB, pszSQL);
    4784          23 :                 sqlite3_free(pszSQL);
    4785             :             }
    4786           5 :             else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
    4787             :             {
    4788           1 :                 bCanWriteAreaOrPoint = false;
    4789           1 :                 char *pszSQL = sqlite3_mprintf(
    4790             :                     "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
    4791             :                     "grid_cell_encoding = 'grid-value-is-center' WHERE "
    4792             :                     "lower(tile_matrix_set_name) = lower('%q')",
    4793             :                     m_osRasterTable.c_str());
    4794           1 :                 SQLCommand(hDB, pszSQL);
    4795           1 :                 sqlite3_free(pszSQL);
    4796             :             }
    4797             :         }
    4798             :     }
    4799             : 
    4800         340 :     char **papszMDDup = nullptr;
    4801         543 :     for (char **papszIter = GDALGeoPackageDataset::GetMetadata();
    4802         543 :          papszIter && *papszIter; ++papszIter)
    4803             :     {
    4804         203 :         if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
    4805          29 :             continue;
    4806         174 :         if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
    4807           8 :             continue;
    4808         166 :         if (STARTS_WITH_CI(*papszIter, "ZOOM_LEVEL="))
    4809          14 :             continue;
    4810         152 :         if (STARTS_WITH_CI(*papszIter, "GPKG_METADATA_ITEM_"))
    4811           4 :             continue;
    4812         148 :         if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
    4813          29 :             !bCanWriteAreaOrPoint &&
    4814          26 :             STARTS_WITH_CI(*papszIter, GDALMD_AREA_OR_POINT))
    4815             :         {
    4816          26 :             continue;
    4817             :         }
    4818         122 :         papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4819             :     }
    4820             : 
    4821         340 :     CPLXMLNode *psXMLNode = nullptr;
    4822             :     {
    4823         340 :         GDALMultiDomainMetadata oLocalMDMD;
    4824         340 :         CSLConstList papszDomainList = oMDMD.GetDomainList();
    4825         340 :         CSLConstList papszIter = papszDomainList;
    4826         340 :         oLocalMDMD.SetMetadata(papszMDDup);
    4827         663 :         while (papszIter && *papszIter)
    4828             :         {
    4829         323 :             if (!EQUAL(*papszIter, "") &&
    4830         157 :                 !EQUAL(*papszIter, "IMAGE_STRUCTURE") &&
    4831          15 :                 !EQUAL(*papszIter, "GEOPACKAGE"))
    4832             :             {
    4833           8 :                 oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
    4834             :                                        *papszIter);
    4835             :             }
    4836         323 :             papszIter++;
    4837             :         }
    4838         340 :         if (m_nBandCountFromMetadata > 0)
    4839             :         {
    4840          72 :             oLocalMDMD.SetMetadataItem(
    4841             :                 "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
    4842             :                 "IMAGE_STRUCTURE");
    4843          72 :             if (nBands == 1)
    4844             :             {
    4845          48 :                 const auto poCT = GetRasterBand(1)->GetColorTable();
    4846          48 :                 if (poCT)
    4847             :                 {
    4848          16 :                     std::string osVal("{");
    4849           8 :                     const int nColorCount = poCT->GetColorEntryCount();
    4850        2056 :                     for (int i = 0; i < nColorCount; ++i)
    4851             :                     {
    4852        2048 :                         if (i > 0)
    4853        2040 :                             osVal += ',';
    4854        2048 :                         const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
    4855             :                         osVal +=
    4856        2048 :                             CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
    4857        2048 :                                        psEntry->c2, psEntry->c3, psEntry->c4);
    4858             :                     }
    4859           8 :                     osVal += '}';
    4860           8 :                     oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
    4861             :                                                "IMAGE_STRUCTURE");
    4862             :                 }
    4863             :             }
    4864          72 :             if (nBands == 1)
    4865             :             {
    4866          48 :                 const char *pszTILE_FORMAT = nullptr;
    4867          48 :                 switch (m_eTF)
    4868             :                 {
    4869           0 :                     case GPKG_TF_PNG_JPEG:
    4870           0 :                         pszTILE_FORMAT = "JPEG_PNG";
    4871           0 :                         break;
    4872          42 :                     case GPKG_TF_PNG:
    4873          42 :                         break;
    4874           0 :                     case GPKG_TF_PNG8:
    4875           0 :                         pszTILE_FORMAT = "PNG8";
    4876           0 :                         break;
    4877           3 :                     case GPKG_TF_JPEG:
    4878           3 :                         pszTILE_FORMAT = "JPEG";
    4879           3 :                         break;
    4880           3 :                     case GPKG_TF_WEBP:
    4881           3 :                         pszTILE_FORMAT = "WEBP";
    4882           3 :                         break;
    4883           0 :                     case GPKG_TF_PNG_16BIT:
    4884           0 :                         break;
    4885           0 :                     case GPKG_TF_TIFF_32BIT_FLOAT:
    4886           0 :                         break;
    4887             :                 }
    4888          48 :                 if (pszTILE_FORMAT)
    4889           6 :                     oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
    4890             :                                                "IMAGE_STRUCTURE");
    4891             :             }
    4892             :         }
    4893         482 :         if (GetRasterCount() > 0 &&
    4894         142 :             GetRasterBand(1)->GetRasterDataType() == GDT_Byte)
    4895             :         {
    4896         112 :             int bHasNoData = FALSE;
    4897             :             const double dfNoDataValue =
    4898         112 :                 GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    4899         112 :             if (bHasNoData)
    4900             :             {
    4901           3 :                 oLocalMDMD.SetMetadataItem("NODATA_VALUE",
    4902             :                                            CPLSPrintf("%.17g", dfNoDataValue),
    4903             :                                            "IMAGE_STRUCTURE");
    4904             :             }
    4905             :         }
    4906         587 :         for (int i = 1; i <= GetRasterCount(); ++i)
    4907             :         {
    4908             :             auto poBand =
    4909         247 :                 cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
    4910         247 :             poBand->AddImplicitStatistics(false);
    4911         247 :             char **papszMD = GetRasterBand(i)->GetMetadata();
    4912         247 :             poBand->AddImplicitStatistics(true);
    4913         247 :             if (papszMD)
    4914             :             {
    4915          14 :                 oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
    4916             :             }
    4917             :         }
    4918         340 :         psXMLNode = oLocalMDMD.Serialize();
    4919             :     }
    4920             : 
    4921         340 :     CSLDestroy(papszMDDup);
    4922         340 :     papszMDDup = nullptr;
    4923             : 
    4924         340 :     WriteMetadata(psXMLNode, m_osRasterTable.c_str());
    4925             : 
    4926         340 :     if (!m_osRasterTable.empty())
    4927             :     {
    4928             :         char **papszGeopackageMD =
    4929         142 :             GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
    4930             : 
    4931         142 :         papszMDDup = nullptr;
    4932         151 :         for (char **papszIter = papszGeopackageMD; papszIter && *papszIter;
    4933             :              ++papszIter)
    4934             :         {
    4935           9 :             papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4936             :         }
    4937             : 
    4938         284 :         GDALMultiDomainMetadata oLocalMDMD;
    4939         142 :         oLocalMDMD.SetMetadata(papszMDDup);
    4940         142 :         CSLDestroy(papszMDDup);
    4941         142 :         papszMDDup = nullptr;
    4942         142 :         psXMLNode = oLocalMDMD.Serialize();
    4943             : 
    4944         142 :         WriteMetadata(psXMLNode, nullptr);
    4945             :     }
    4946             : 
    4947         552 :     for (int i = 0; i < m_nLayers; i++)
    4948             :     {
    4949             :         const char *pszIdentifier =
    4950         212 :             m_papoLayers[i]->GetMetadataItem("IDENTIFIER");
    4951             :         const char *pszDescription =
    4952         212 :             m_papoLayers[i]->GetMetadataItem("DESCRIPTION");
    4953         212 :         if (pszIdentifier != nullptr)
    4954             :         {
    4955             :             char *pszSQL =
    4956           3 :                 sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
    4957             :                                 "WHERE lower(table_name) = lower('%q')",
    4958           3 :                                 pszIdentifier, m_papoLayers[i]->GetName());
    4959           3 :             SQLCommand(hDB, pszSQL);
    4960           3 :             sqlite3_free(pszSQL);
    4961             :         }
    4962         212 :         if (pszDescription != nullptr)
    4963             :         {
    4964             :             char *pszSQL =
    4965           3 :                 sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
    4966             :                                 "WHERE lower(table_name) = lower('%q')",
    4967           3 :                                 pszDescription, m_papoLayers[i]->GetName());
    4968           3 :             SQLCommand(hDB, pszSQL);
    4969           3 :             sqlite3_free(pszSQL);
    4970             :         }
    4971             : 
    4972         212 :         papszMDDup = nullptr;
    4973         588 :         for (char **papszIter = m_papoLayers[i]->GetMetadata();
    4974         588 :              papszIter && *papszIter; ++papszIter)
    4975             :         {
    4976         376 :             if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
    4977           3 :                 continue;
    4978         373 :             if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
    4979           3 :                 continue;
    4980         370 :             if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
    4981           0 :                 continue;
    4982         370 :             papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4983             :         }
    4984             : 
    4985             :         {
    4986         212 :             GDALMultiDomainMetadata oLocalMDMD;
    4987         212 :             char **papszDomainList = m_papoLayers[i]->GetMetadataDomainList();
    4988         212 :             char **papszIter = papszDomainList;
    4989         212 :             oLocalMDMD.SetMetadata(papszMDDup);
    4990         465 :             while (papszIter && *papszIter)
    4991             :             {
    4992         253 :                 if (!EQUAL(*papszIter, ""))
    4993         108 :                     oLocalMDMD.SetMetadata(
    4994          54 :                         m_papoLayers[i]->GetMetadata(*papszIter), *papszIter);
    4995         253 :                 papszIter++;
    4996             :             }
    4997         212 :             CSLDestroy(papszDomainList);
    4998         212 :             psXMLNode = oLocalMDMD.Serialize();
    4999             :         }
    5000             : 
    5001         212 :         CSLDestroy(papszMDDup);
    5002         212 :         papszMDDup = nullptr;
    5003             : 
    5004         212 :         WriteMetadata(psXMLNode, m_papoLayers[i]->GetName());
    5005             :     }
    5006             : }
    5007             : 
    5008             : /************************************************************************/
    5009             : /*                          GetMetadataItem()                           */
    5010             : /************************************************************************/
    5011             : 
    5012        1517 : const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
    5013             :                                                    const char *pszDomain)
    5014             : {
    5015        1517 :     pszDomain = CheckMetadataDomain(pszDomain);
    5016        1517 :     return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
    5017             : }
    5018             : 
    5019             : /************************************************************************/
    5020             : /*                            SetMetadata()                             */
    5021             : /************************************************************************/
    5022             : 
    5023         131 : CPLErr GDALGeoPackageDataset::SetMetadata(char **papszMetadata,
    5024             :                                           const char *pszDomain)
    5025             : {
    5026         131 :     pszDomain = CheckMetadataDomain(pszDomain);
    5027         131 :     m_bMetadataDirty = true;
    5028         131 :     GetMetadata(); /* force loading from storage if needed */
    5029         131 :     return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
    5030             : }
    5031             : 
    5032             : /************************************************************************/
    5033             : /*                          SetMetadataItem()                           */
    5034             : /************************************************************************/
    5035             : 
    5036          21 : CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
    5037             :                                               const char *pszValue,
    5038             :                                               const char *pszDomain)
    5039             : {
    5040          21 :     pszDomain = CheckMetadataDomain(pszDomain);
    5041          21 :     m_bMetadataDirty = true;
    5042          21 :     GetMetadata(); /* force loading from storage if needed */
    5043          21 :     return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    5044             : }
    5045             : 
    5046             : /************************************************************************/
    5047             : /*                                Create()                              */
    5048             : /************************************************************************/
    5049             : 
    5050         858 : int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
    5051             :                                   int nYSize, int nBandsIn, GDALDataType eDT,
    5052             :                                   char **papszOptions)
    5053             : {
    5054        1716 :     CPLString osCommand;
    5055             : 
    5056             :     /* First, ensure there isn't any such file yet. */
    5057             :     VSIStatBufL sStatBuf;
    5058             : 
    5059         858 :     if (nBandsIn != 0)
    5060             :     {
    5061         220 :         if (eDT == GDT_Byte)
    5062             :         {
    5063         150 :             if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
    5064             :                 nBandsIn != 4)
    5065             :             {
    5066           1 :                 CPLError(CE_Failure, CPLE_NotSupported,
    5067             :                          "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
    5068             :                          "3 (RGB) or 4 (RGBA) band dataset supported for "
    5069             :                          "Byte datatype");
    5070           1 :                 return FALSE;
    5071             :             }
    5072             :         }
    5073          70 :         else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
    5074             :         {
    5075          43 :             if (nBandsIn != 1)
    5076             :             {
    5077           3 :                 CPLError(CE_Failure, CPLE_NotSupported,
    5078             :                          "Only single band dataset supported for non Byte "
    5079             :                          "datatype");
    5080           3 :                 return FALSE;
    5081             :             }
    5082             :         }
    5083             :         else
    5084             :         {
    5085          27 :             CPLError(CE_Failure, CPLE_NotSupported,
    5086             :                      "Only Byte, Int16, UInt16 or Float32 supported");
    5087          27 :             return FALSE;
    5088             :         }
    5089             :     }
    5090             : 
    5091         827 :     const size_t nFilenameLen = strlen(pszFilename);
    5092         827 :     const bool bGpkgZip =
    5093         822 :         (nFilenameLen > strlen(".gpkg.zip") &&
    5094        1649 :          !STARTS_WITH(pszFilename, "/vsizip/") &&
    5095         822 :          EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
    5096             : 
    5097             :     const bool bUseTempFile =
    5098         828 :         bGpkgZip || (CPLTestBool(CPLGetConfigOption(
    5099           1 :                          "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
    5100           1 :                      (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
    5101           1 :                       EQUAL(CPLGetConfigOption(
    5102             :                                 "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
    5103         827 :                             "FORCED")));
    5104             : 
    5105         827 :     bool bFileExists = false;
    5106         827 :     if (VSIStatL(pszFilename, &sStatBuf) == 0)
    5107             :     {
    5108           8 :         bFileExists = true;
    5109          16 :         if (nBandsIn == 0 || bUseTempFile ||
    5110           8 :             !CPLTestBool(
    5111             :                 CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
    5112             :         {
    5113           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5114             :                      "A file system object called '%s' already exists.",
    5115             :                      pszFilename);
    5116             : 
    5117           0 :             return FALSE;
    5118             :         }
    5119             :     }
    5120             : 
    5121         827 :     if (bUseTempFile)
    5122             :     {
    5123           3 :         if (bGpkgZip)
    5124             :         {
    5125           2 :             std::string osFilenameInZip(CPLGetFilename(pszFilename));
    5126           2 :             osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
    5127             :             m_osFinalFilename =
    5128           2 :                 std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
    5129             :         }
    5130             :         else
    5131             :         {
    5132           1 :             m_osFinalFilename = pszFilename;
    5133             :         }
    5134           3 :         m_pszFilename = CPLStrdup(
    5135           6 :             CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename)).c_str());
    5136           3 :         CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
    5137             :     }
    5138             :     else
    5139             :     {
    5140         824 :         m_pszFilename = CPLStrdup(pszFilename);
    5141             :     }
    5142         827 :     m_bNew = true;
    5143         827 :     eAccess = GA_Update;
    5144         827 :     m_bDateTimeWithTZ =
    5145         827 :         EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
    5146             :               "WITH_TZ");
    5147             : 
    5148             :     // for test/debug purposes only. true is the nominal value
    5149         827 :     m_bPNGSupports2Bands =
    5150         827 :         CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
    5151         827 :     m_bPNGSupportsCT =
    5152         827 :         CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
    5153             : 
    5154         827 :     if (!OpenOrCreateDB(bFileExists
    5155             :                             ? SQLITE_OPEN_READWRITE
    5156             :                             : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
    5157           5 :         return FALSE;
    5158             : 
    5159             :     /* Default to synchronous=off for performance for new file */
    5160        1636 :     if (!bFileExists &&
    5161         814 :         CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
    5162             :     {
    5163         322 :         SQLCommand(hDB, "PRAGMA synchronous = OFF");
    5164             :     }
    5165             : 
    5166             :     /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
    5167             :     /* will be written into the main file and supported henceforth */
    5168         822 :     SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
    5169             : 
    5170         822 :     if (bFileExists)
    5171             :     {
    5172           8 :         VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
    5173           8 :         if (fp)
    5174             :         {
    5175             :             GByte abyHeader[100];
    5176           8 :             VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
    5177           8 :             VSIFCloseL(fp);
    5178             : 
    5179           8 :             memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
    5180           8 :             m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
    5181           8 :             memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
    5182           8 :             m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
    5183             : 
    5184           8 :             if (m_nApplicationId == GP10_APPLICATION_ID)
    5185             :             {
    5186           0 :                 CPLDebug("GPKG", "GeoPackage v1.0");
    5187             :             }
    5188           8 :             else if (m_nApplicationId == GP11_APPLICATION_ID)
    5189             :             {
    5190           0 :                 CPLDebug("GPKG", "GeoPackage v1.1");
    5191             :             }
    5192           8 :             else if (m_nApplicationId == GPKG_APPLICATION_ID &&
    5193           8 :                      m_nUserVersion >= GPKG_1_2_VERSION)
    5194             :             {
    5195           8 :                 CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
    5196           8 :                          (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
    5197             :             }
    5198             :         }
    5199             : 
    5200           8 :         DetectSpatialRefSysColumns();
    5201             :     }
    5202             : 
    5203         822 :     const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
    5204         822 :     if (pszVersion && !EQUAL(pszVersion, "AUTO"))
    5205             :     {
    5206          33 :         if (EQUAL(pszVersion, "1.0"))
    5207             :         {
    5208           2 :             m_nApplicationId = GP10_APPLICATION_ID;
    5209           2 :             m_nUserVersion = 0;
    5210             :         }
    5211          31 :         else if (EQUAL(pszVersion, "1.1"))
    5212             :         {
    5213           1 :             m_nApplicationId = GP11_APPLICATION_ID;
    5214           1 :             m_nUserVersion = 0;
    5215             :         }
    5216          30 :         else if (EQUAL(pszVersion, "1.2"))
    5217             :         {
    5218          12 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5219          12 :             m_nUserVersion = GPKG_1_2_VERSION;
    5220             :         }
    5221          18 :         else if (EQUAL(pszVersion, "1.3"))
    5222             :         {
    5223           3 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5224           3 :             m_nUserVersion = GPKG_1_3_VERSION;
    5225             :         }
    5226          15 :         else if (EQUAL(pszVersion, "1.4"))
    5227             :         {
    5228          15 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5229          15 :             m_nUserVersion = GPKG_1_4_VERSION;
    5230             :         }
    5231             :     }
    5232             : 
    5233         822 :     SoftStartTransaction();
    5234             : 
    5235        1644 :     CPLString osSQL;
    5236         822 :     if (!bFileExists)
    5237             :     {
    5238             :         /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
    5239             :          * table */
    5240             :         /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5241             :         osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
    5242             :                 "srs_name TEXT NOT NULL,"
    5243             :                 "srs_id INTEGER NOT NULL PRIMARY KEY,"
    5244             :                 "organization TEXT NOT NULL,"
    5245             :                 "organization_coordsys_id INTEGER NOT NULL,"
    5246             :                 "definition  TEXT NOT NULL,"
    5247         814 :                 "description TEXT";
    5248         814 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
    5249         991 :                                              "NO")) ||
    5250         177 :             (nBandsIn != 0 && eDT != GDT_Byte))
    5251             :         {
    5252          42 :             m_bHasDefinition12_063 = true;
    5253          42 :             osSQL += ", definition_12_063 TEXT NOT NULL";
    5254          42 :             if (m_nUserVersion >= GPKG_1_4_VERSION)
    5255             :             {
    5256           1 :                 osSQL += ", epoch DOUBLE";
    5257           1 :                 m_bHasEpochColumn = true;
    5258             :             }
    5259             :         }
    5260             :         osSQL += ")"
    5261             :                  ";"
    5262             :                  /* Requirement 11: The gpkg_spatial_ref_sys table in a
    5263             :                     GeoPackage SHALL */
    5264             :                  /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
    5265             :                  /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5266             : 
    5267             :                  "INSERT INTO gpkg_spatial_ref_sys ("
    5268             :                  "srs_name, srs_id, organization, organization_coordsys_id, "
    5269         814 :                  "definition, description";
    5270         814 :         if (m_bHasDefinition12_063)
    5271          42 :             osSQL += ", definition_12_063";
    5272             :         osSQL +=
    5273             :             ") VALUES ("
    5274             :             "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
    5275             :             "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
    5276             :             "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
    5277             :             "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
    5278             :             "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
    5279             :             "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
    5280             :             "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
    5281             :             "', 'longitude/latitude coordinates in decimal degrees on the WGS "
    5282         814 :             "84 spheroid'";
    5283         814 :         if (m_bHasDefinition12_063)
    5284             :             osSQL +=
    5285             :                 ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
    5286             :                 "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
    5287             :                 "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
    5288             :                 "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
    5289             :                 "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
    5290             :                 "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
    5291          42 :                 "ID[\"EPSG\", 4326]]'";
    5292             :         osSQL +=
    5293             :             ")"
    5294             :             ";"
    5295             :             /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
    5296             :                SHALL */
    5297             :             /* contain a record with an srs_id of -1, an organization of “NONE”,
    5298             :              */
    5299             :             /* an organization_coordsys_id of -1, and definition “undefined” */
    5300             :             /* for undefined Cartesian coordinate reference systems */
    5301             :             /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5302             :             "INSERT INTO gpkg_spatial_ref_sys ("
    5303             :             "srs_name, srs_id, organization, organization_coordsys_id, "
    5304         814 :             "definition, description";
    5305         814 :         if (m_bHasDefinition12_063)
    5306          42 :             osSQL += ", definition_12_063";
    5307             :         osSQL += ") VALUES ("
    5308             :                  "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
    5309         814 :                  "'undefined Cartesian coordinate reference system'";
    5310         814 :         if (m_bHasDefinition12_063)
    5311          42 :             osSQL += ", 'undefined'";
    5312             :         osSQL +=
    5313             :             ")"
    5314             :             ";"
    5315             :             /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
    5316             :                SHALL */
    5317             :             /* contain a record with an srs_id of 0, an organization of “NONE”,
    5318             :              */
    5319             :             /* an organization_coordsys_id of 0, and definition “undefined” */
    5320             :             /* for undefined geographic coordinate reference systems */
    5321             :             /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5322             :             "INSERT INTO gpkg_spatial_ref_sys ("
    5323             :             "srs_name, srs_id, organization, organization_coordsys_id, "
    5324         814 :             "definition, description";
    5325         814 :         if (m_bHasDefinition12_063)
    5326          42 :             osSQL += ", definition_12_063";
    5327             :         osSQL += ") VALUES ("
    5328             :                  "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
    5329         814 :                  "'undefined geographic coordinate reference system'";
    5330         814 :         if (m_bHasDefinition12_063)
    5331          42 :             osSQL += ", 'undefined'";
    5332             :         osSQL += ")"
    5333             :                  ";"
    5334             :                  /* Requirement 13: A GeoPackage file SHALL include a
    5335             :                     gpkg_contents table */
    5336             :                  /* http://opengis.github.io/geopackage/#_contents */
    5337             :                  "CREATE TABLE gpkg_contents ("
    5338             :                  "table_name TEXT NOT NULL PRIMARY KEY,"
    5339             :                  "data_type TEXT NOT NULL,"
    5340             :                  "identifier TEXT UNIQUE,"
    5341             :                  "description TEXT DEFAULT '',"
    5342             :                  "last_change DATETIME NOT NULL DEFAULT "
    5343             :                  "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
    5344             :                  "min_x DOUBLE, min_y DOUBLE,"
    5345             :                  "max_x DOUBLE, max_y DOUBLE,"
    5346             :                  "srs_id INTEGER,"
    5347             :                  "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
    5348             :                  "gpkg_spatial_ref_sys(srs_id)"
    5349         814 :                  ")";
    5350             : 
    5351             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    5352         814 :         if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
    5353             :         {
    5354         809 :             m_bHasGPKGOGRContents = true;
    5355             :             osSQL += ";"
    5356             :                      "CREATE TABLE gpkg_ogr_contents("
    5357             :                      "table_name TEXT NOT NULL PRIMARY KEY,"
    5358             :                      "feature_count INTEGER DEFAULT NULL"
    5359         809 :                      ")";
    5360             :         }
    5361             : #endif
    5362             : 
    5363             :         /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
    5364             :          * “features” */
    5365             :         /* data_type SHALL contain a gpkg_geometry_columns table or updateable
    5366             :          * view */
    5367             :         /* http://opengis.github.io/geopackage/#_geometry_columns */
    5368             :         const bool bCreateGeometryColumns =
    5369         814 :             CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
    5370         814 :         if (bCreateGeometryColumns)
    5371             :         {
    5372         813 :             m_bHasGPKGGeometryColumns = true;
    5373         813 :             osSQL += ";";
    5374         813 :             osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
    5375             :         }
    5376             :     }
    5377             : 
    5378             :     const bool bCreateTriggers =
    5379         822 :         CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
    5380           8 :     if ((bFileExists && nBandsIn != 0 &&
    5381           8 :          SQLGetInteger(
    5382             :              hDB,
    5383             :              "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
    5384             :              "AND type in ('table', 'view')",
    5385        1644 :              nullptr) == 0) ||
    5386         821 :         (!bFileExists &&
    5387         814 :          CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
    5388             :     {
    5389         814 :         if (!osSQL.empty())
    5390         813 :             osSQL += ";";
    5391             : 
    5392             :         /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
    5393             :          * Creation SQL  */
    5394             :         osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
    5395             :                  "table_name TEXT NOT NULL PRIMARY KEY,"
    5396             :                  "srs_id INTEGER NOT NULL,"
    5397             :                  "min_x DOUBLE NOT NULL,"
    5398             :                  "min_y DOUBLE NOT NULL,"
    5399             :                  "max_x DOUBLE NOT NULL,"
    5400             :                  "max_y DOUBLE NOT NULL,"
    5401             :                  "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
    5402             :                  "REFERENCES gpkg_contents(table_name),"
    5403             :                  "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
    5404             :                  "gpkg_spatial_ref_sys (srs_id)"
    5405             :                  ")"
    5406             :                  ";"
    5407             : 
    5408             :                  /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
    5409             :                     Creation SQL */
    5410             :                  "CREATE TABLE gpkg_tile_matrix ("
    5411             :                  "table_name TEXT NOT NULL,"
    5412             :                  "zoom_level INTEGER NOT NULL,"
    5413             :                  "matrix_width INTEGER NOT NULL,"
    5414             :                  "matrix_height INTEGER NOT NULL,"
    5415             :                  "tile_width INTEGER NOT NULL,"
    5416             :                  "tile_height INTEGER NOT NULL,"
    5417             :                  "pixel_x_size DOUBLE NOT NULL,"
    5418             :                  "pixel_y_size DOUBLE NOT NULL,"
    5419             :                  "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
    5420             :                  "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
    5421             :                  "REFERENCES gpkg_contents(table_name)"
    5422         814 :                  ")";
    5423             : 
    5424         814 :         if (bCreateTriggers)
    5425             :         {
    5426             :             /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
    5427             :              * Definition SQL */
    5428         814 :             const char *pszTileMatrixTrigger =
    5429             :                 "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
    5430             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5431             :                 "FOR EACH ROW BEGIN "
    5432             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5433             :                 "violates constraint: zoom_level cannot be less than 0') "
    5434             :                 "WHERE (NEW.zoom_level < 0); "
    5435             :                 "END; "
    5436             :                 "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
    5437             :                 "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
    5438             :                 "FOR EACH ROW BEGIN "
    5439             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5440             :                 "violates constraint: zoom_level cannot be less than 0') "
    5441             :                 "WHERE (NEW.zoom_level < 0); "
    5442             :                 "END; "
    5443             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
    5444             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5445             :                 "FOR EACH ROW BEGIN "
    5446             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5447             :                 "violates constraint: matrix_width cannot be less than 1') "
    5448             :                 "WHERE (NEW.matrix_width < 1); "
    5449             :                 "END; "
    5450             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
    5451             :                 "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
    5452             :                 "FOR EACH ROW BEGIN "
    5453             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5454             :                 "violates constraint: matrix_width cannot be less than 1') "
    5455             :                 "WHERE (NEW.matrix_width < 1); "
    5456             :                 "END; "
    5457             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
    5458             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5459             :                 "FOR EACH ROW BEGIN "
    5460             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5461             :                 "violates constraint: matrix_height cannot be less than 1') "
    5462             :                 "WHERE (NEW.matrix_height < 1); "
    5463             :                 "END; "
    5464             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
    5465             :                 "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
    5466             :                 "FOR EACH ROW BEGIN "
    5467             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5468             :                 "violates constraint: matrix_height cannot be less than 1') "
    5469             :                 "WHERE (NEW.matrix_height < 1); "
    5470             :                 "END; "
    5471             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
    5472             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5473             :                 "FOR EACH ROW BEGIN "
    5474             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5475             :                 "violates constraint: pixel_x_size must be greater than 0') "
    5476             :                 "WHERE NOT (NEW.pixel_x_size > 0); "
    5477             :                 "END; "
    5478             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
    5479             :                 "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
    5480             :                 "FOR EACH ROW BEGIN "
    5481             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5482             :                 "violates constraint: pixel_x_size must be greater than 0') "
    5483             :                 "WHERE NOT (NEW.pixel_x_size > 0); "
    5484             :                 "END; "
    5485             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
    5486             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5487             :                 "FOR EACH ROW BEGIN "
    5488             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5489             :                 "violates constraint: pixel_y_size must be greater than 0') "
    5490             :                 "WHERE NOT (NEW.pixel_y_size > 0); "
    5491             :                 "END; "
    5492             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
    5493             :                 "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
    5494             :                 "FOR EACH ROW BEGIN "
    5495             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5496             :                 "violates constraint: pixel_y_size must be greater than 0') "
    5497             :                 "WHERE NOT (NEW.pixel_y_size > 0); "
    5498             :                 "END;";
    5499         814 :             osSQL += ";";
    5500         814 :             osSQL += pszTileMatrixTrigger;
    5501             :         }
    5502             :     }
    5503             : 
    5504         822 :     if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
    5505           1 :         return FALSE;
    5506             : 
    5507         821 :     if (!bFileExists)
    5508             :     {
    5509             :         const char *pszMetadataTables =
    5510         813 :             CSLFetchNameValue(papszOptions, "METADATA_TABLES");
    5511         813 :         if (pszMetadataTables)
    5512           6 :             m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
    5513             : 
    5514         813 :         if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
    5515           0 :             return FALSE;
    5516             : 
    5517         813 :         if (m_bHasDefinition12_063)
    5518             :         {
    5519          84 :             if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
    5520             :                 OGRERR_NONE !=
    5521          42 :                     SQLCommand(hDB, "INSERT INTO gpkg_extensions "
    5522             :                                     "(table_name, column_name, extension_name, "
    5523             :                                     "definition, scope) "
    5524             :                                     "VALUES "
    5525             :                                     "('gpkg_spatial_ref_sys', "
    5526             :                                     "'definition_12_063', 'gpkg_crs_wkt', "
    5527             :                                     "'http://www.geopackage.org/spec120/"
    5528             :                                     "#extension_crs_wkt', 'read-write')"))
    5529             :             {
    5530           0 :                 return FALSE;
    5531             :             }
    5532          42 :             if (m_bHasEpochColumn)
    5533             :             {
    5534           1 :                 if (OGRERR_NONE !=
    5535           1 :                         SQLCommand(
    5536             :                             hDB, "UPDATE gpkg_extensions SET extension_name = "
    5537             :                                  "'gpkg_crs_wkt_1_1' "
    5538           2 :                                  "WHERE extension_name = 'gpkg_crs_wkt'") ||
    5539             :                     OGRERR_NONE !=
    5540           1 :                         SQLCommand(hDB, "INSERT INTO gpkg_extensions "
    5541             :                                         "(table_name, column_name, "
    5542             :                                         "extension_name, definition, scope) "
    5543             :                                         "VALUES "
    5544             :                                         "('gpkg_spatial_ref_sys', 'epoch', "
    5545             :                                         "'gpkg_crs_wkt_1_1', "
    5546             :                                         "'http://www.geopackage.org/spec/"
    5547             :                                         "#extension_crs_wkt', "
    5548             :                                         "'read-write')"))
    5549             :                 {
    5550           0 :                     return FALSE;
    5551             :                 }
    5552             :             }
    5553             :         }
    5554             :     }
    5555             : 
    5556         821 :     if (nBandsIn != 0)
    5557             :     {
    5558         184 :         const std::string osTableName = CPLGetBasenameSafe(m_pszFilename);
    5559             :         m_osRasterTable = CSLFetchNameValueDef(papszOptions, "RASTER_TABLE",
    5560         184 :                                                osTableName.c_str());
    5561         184 :         if (m_osRasterTable.empty())
    5562             :         {
    5563           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5564             :                      "RASTER_TABLE must be set to a non empty value");
    5565           0 :             return FALSE;
    5566             :         }
    5567         184 :         m_bIdentifierAsCO =
    5568         184 :             CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
    5569             :         m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
    5570         184 :                                               m_osRasterTable);
    5571         184 :         m_bDescriptionAsCO =
    5572         184 :             CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
    5573             :         m_osDescription =
    5574         184 :             CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
    5575         184 :         SetDataType(eDT);
    5576         184 :         if (eDT == GDT_Int16)
    5577          16 :             SetGlobalOffsetScale(-32768.0, 1.0);
    5578             : 
    5579             :         /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
    5580             :          * table Create Table SQL (Informative) */
    5581             :         char *pszSQL =
    5582         184 :             sqlite3_mprintf("CREATE TABLE \"%w\" ("
    5583             :                             "id INTEGER PRIMARY KEY AUTOINCREMENT,"
    5584             :                             "zoom_level INTEGER NOT NULL,"
    5585             :                             "tile_column INTEGER NOT NULL,"
    5586             :                             "tile_row INTEGER NOT NULL,"
    5587             :                             "tile_data BLOB NOT NULL,"
    5588             :                             "UNIQUE (zoom_level, tile_column, tile_row)"
    5589             :                             ")",
    5590             :                             m_osRasterTable.c_str());
    5591         184 :         osSQL = pszSQL;
    5592         184 :         sqlite3_free(pszSQL);
    5593             : 
    5594         184 :         if (bCreateTriggers)
    5595             :         {
    5596         184 :             osSQL += ";";
    5597         184 :             osSQL += CreateRasterTriggersSQL(m_osRasterTable);
    5598             :         }
    5599             : 
    5600         184 :         OGRErr eErr = SQLCommand(hDB, osSQL);
    5601         184 :         if (OGRERR_NONE != eErr)
    5602           0 :             return FALSE;
    5603             : 
    5604         184 :         const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
    5605         184 :         if (eDT == GDT_Int16 || eDT == GDT_UInt16)
    5606             :         {
    5607          27 :             m_eTF = GPKG_TF_PNG_16BIT;
    5608          27 :             if (pszTF)
    5609             :             {
    5610           1 :                 if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
    5611             :                 {
    5612           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    5613             :                              "Only AUTO or PNG supported "
    5614             :                              "as tile format for Int16 / UInt16");
    5615             :                 }
    5616             :             }
    5617             :         }
    5618         157 :         else if (eDT == GDT_Float32)
    5619             :         {
    5620          13 :             m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
    5621          13 :             if (pszTF)
    5622             :             {
    5623           5 :                 if (EQUAL(pszTF, "PNG"))
    5624           5 :                     m_eTF = GPKG_TF_PNG_16BIT;
    5625           0 :                 else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
    5626             :                 {
    5627           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    5628             :                              "Only AUTO, PNG or TIFF supported "
    5629             :                              "as tile format for Float32");
    5630             :                 }
    5631             :             }
    5632             :         }
    5633             :         else
    5634             :         {
    5635         144 :             if (pszTF)
    5636             :             {
    5637          71 :                 m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
    5638          71 :                 if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
    5639           7 :                     m_bMetadataDirty = true;
    5640             :             }
    5641          73 :             else if (nBandsIn == 1)
    5642          62 :                 m_eTF = GPKG_TF_PNG;
    5643             :         }
    5644             : 
    5645         184 :         if (eDT != GDT_Byte)
    5646             :         {
    5647          40 :             if (!CreateTileGriddedTable(papszOptions))
    5648           0 :                 return FALSE;
    5649             :         }
    5650             : 
    5651         184 :         nRasterXSize = nXSize;
    5652         184 :         nRasterYSize = nYSize;
    5653             : 
    5654             :         const char *pszTileSize =
    5655         184 :             CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
    5656             :         const char *pszTileWidth =
    5657         184 :             CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
    5658             :         const char *pszTileHeight =
    5659         184 :             CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
    5660         184 :         int nTileWidth = atoi(pszTileWidth);
    5661         184 :         int nTileHeight = atoi(pszTileHeight);
    5662         184 :         if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
    5663         368 :              nTileHeight > 4096) &&
    5664           1 :             !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
    5665             :         {
    5666           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5667             :                      "Invalid block dimensions: %dx%d", nTileWidth,
    5668             :                      nTileHeight);
    5669           0 :             return FALSE;
    5670             :         }
    5671             : 
    5672         501 :         for (int i = 1; i <= nBandsIn; i++)
    5673         317 :             SetBand(
    5674         317 :                 i, new GDALGeoPackageRasterBand(this, nTileWidth, nTileHeight));
    5675             : 
    5676         184 :         GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
    5677             :                                         "IMAGE_STRUCTURE");
    5678         184 :         GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
    5679         184 :         if (!m_osDescription.empty())
    5680           1 :             GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
    5681             : 
    5682         184 :         ParseCompressionOptions(papszOptions);
    5683             : 
    5684         184 :         if (m_eTF == GPKG_TF_WEBP)
    5685             :         {
    5686          10 :             if (!RegisterWebPExtension())
    5687           0 :                 return FALSE;
    5688             :         }
    5689             : 
    5690             :         m_osTilingScheme =
    5691         184 :             CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
    5692         184 :         if (!EQUAL(m_osTilingScheme, "CUSTOM"))
    5693             :         {
    5694          22 :             const auto poTS = GetTilingScheme(m_osTilingScheme);
    5695          22 :             if (!poTS)
    5696           0 :                 return FALSE;
    5697             : 
    5698          43 :             if (nTileWidth != poTS->nTileWidth ||
    5699          21 :                 nTileHeight != poTS->nTileHeight)
    5700             :             {
    5701           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    5702             :                          "Tile dimension should be %dx%d for %s tiling scheme",
    5703           1 :                          poTS->nTileWidth, poTS->nTileHeight,
    5704             :                          m_osTilingScheme.c_str());
    5705           1 :                 return FALSE;
    5706             :             }
    5707             : 
    5708             :             const char *pszZoomLevel =
    5709          21 :                 CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
    5710          21 :             if (pszZoomLevel)
    5711             :             {
    5712           1 :                 m_nZoomLevel = atoi(pszZoomLevel);
    5713           1 :                 int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
    5714           1 :                 while ((1 << nMaxZoomLevelForThisTM) >
    5715           2 :                            INT_MAX / poTS->nTileXCountZoomLevel0 ||
    5716           1 :                        (1 << nMaxZoomLevelForThisTM) >
    5717           1 :                            INT_MAX / poTS->nTileYCountZoomLevel0)
    5718             :                 {
    5719           0 :                     --nMaxZoomLevelForThisTM;
    5720             :                 }
    5721             : 
    5722           1 :                 if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
    5723             :                 {
    5724           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    5725             :                              "ZOOM_LEVEL = %s is invalid. It should be in "
    5726             :                              "[0,%d] range",
    5727             :                              pszZoomLevel, nMaxZoomLevelForThisTM);
    5728           0 :                     return FALSE;
    5729             :                 }
    5730             :             }
    5731             : 
    5732             :             // Implicitly sets SRS.
    5733          21 :             OGRSpatialReference oSRS;
    5734          21 :             if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
    5735           0 :                 return FALSE;
    5736          21 :             char *pszWKT = nullptr;
    5737          21 :             oSRS.exportToWkt(&pszWKT);
    5738          21 :             SetProjection(pszWKT);
    5739          21 :             CPLFree(pszWKT);
    5740             :         }
    5741             :         else
    5742             :         {
    5743         162 :             if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
    5744             :             {
    5745           0 :                 CPLError(
    5746             :                     CE_Failure, CPLE_NotSupported,
    5747             :                     "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
    5748           0 :                 return false;
    5749             :             }
    5750             :         }
    5751             :     }
    5752             : 
    5753         820 :     if (bFileExists && nBandsIn > 0 && eDT == GDT_Byte)
    5754             :     {
    5755             :         // If there was an ogr_empty_table table, we can remove it
    5756           7 :         RemoveOGREmptyTable();
    5757             :     }
    5758             : 
    5759         820 :     SoftCommitTransaction();
    5760             : 
    5761             :     /* Requirement 2 */
    5762             :     /* We have to do this after there's some content so the database file */
    5763             :     /* is not zero length */
    5764         820 :     SetApplicationAndUserVersionId();
    5765             : 
    5766             :     /* Default to synchronous=off for performance for new file */
    5767        1632 :     if (!bFileExists &&
    5768         812 :         CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
    5769             :     {
    5770         322 :         SQLCommand(hDB, "PRAGMA synchronous = OFF");
    5771             :     }
    5772             : 
    5773         820 :     return TRUE;
    5774             : }
    5775             : 
    5776             : /************************************************************************/
    5777             : /*                        RemoveOGREmptyTable()                         */
    5778             : /************************************************************************/
    5779             : 
    5780         635 : void GDALGeoPackageDataset::RemoveOGREmptyTable()
    5781             : {
    5782             :     // Run with sqlite3_exec since we don't want errors to be emitted
    5783         635 :     sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
    5784             :                  nullptr);
    5785         635 :     sqlite3_exec(
    5786             :         hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
    5787             :         nullptr, nullptr, nullptr);
    5788             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    5789         635 :     if (m_bHasGPKGOGRContents)
    5790             :     {
    5791         623 :         sqlite3_exec(hDB,
    5792             :                      "DELETE FROM gpkg_ogr_contents WHERE "
    5793             :                      "table_name = 'ogr_empty_table'",
    5794             :                      nullptr, nullptr, nullptr);
    5795             :     }
    5796             : #endif
    5797         635 :     sqlite3_exec(hDB,
    5798             :                  "DELETE FROM gpkg_geometry_columns WHERE "
    5799             :                  "table_name = 'ogr_empty_table'",
    5800             :                  nullptr, nullptr, nullptr);
    5801         635 : }
    5802             : 
    5803             : /************************************************************************/
    5804             : /*                        CreateTileGriddedTable()                      */
    5805             : /************************************************************************/
    5806             : 
    5807          40 : bool GDALGeoPackageDataset::CreateTileGriddedTable(char **papszOptions)
    5808             : {
    5809          80 :     CPLString osSQL;
    5810          40 :     if (!HasGriddedCoverageAncillaryTable())
    5811             :     {
    5812             :         // It doesn't exist. So create gpkg_extensions table if necessary, and
    5813             :         // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
    5814             :         // and register them as extensions.
    5815          40 :         if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    5816           0 :             return false;
    5817             : 
    5818             :         // Req 1 /table-defs/coverage-ancillary
    5819             :         osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
    5820             :                 "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
    5821             :                 "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
    5822             :                 "datatype TEXT NOT NULL DEFAULT 'integer',"
    5823             :                 "scale REAL NOT NULL DEFAULT 1.0,"
    5824             :                 "offset REAL NOT NULL DEFAULT 0.0,"
    5825             :                 "precision REAL DEFAULT 1.0,"
    5826             :                 "data_null REAL,"
    5827             :                 "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
    5828             :                 "uom TEXT,"
    5829             :                 "field_name TEXT DEFAULT 'Height',"
    5830             :                 "quantity_definition TEXT DEFAULT 'Height',"
    5831             :                 "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
    5832             :                 "REFERENCES gpkg_tile_matrix_set ( table_name ) "
    5833             :                 "CHECK (datatype in ('integer','float')))"
    5834             :                 ";"
    5835             :                 // Requirement 2 /table-defs/tile-ancillary
    5836             :                 "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
    5837             :                 "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
    5838             :                 "tpudt_name TEXT NOT NULL,"
    5839             :                 "tpudt_id INTEGER NOT NULL,"
    5840             :                 "scale REAL NOT NULL DEFAULT 1.0,"
    5841             :                 "offset REAL NOT NULL DEFAULT 0.0,"
    5842             :                 "min REAL DEFAULT NULL,"
    5843             :                 "max REAL DEFAULT NULL,"
    5844             :                 "mean REAL DEFAULT NULL,"
    5845             :                 "std_dev REAL DEFAULT NULL,"
    5846             :                 "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
    5847             :                 "REFERENCES gpkg_contents(table_name),"
    5848             :                 "UNIQUE (tpudt_name, tpudt_id))"
    5849             :                 ";"
    5850             :                 // Requirement 6 /gpkg-extensions
    5851             :                 "INSERT INTO gpkg_extensions "
    5852             :                 "(table_name, column_name, extension_name, definition, scope) "
    5853             :                 "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
    5854             :                 "'gpkg_2d_gridded_coverage', "
    5855             :                 "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5856             :                 "'read-write')"
    5857             :                 ";"
    5858             :                 // Requirement 6 /gpkg-extensions
    5859             :                 "INSERT INTO gpkg_extensions "
    5860             :                 "(table_name, column_name, extension_name, definition, scope) "
    5861             :                 "VALUES ('gpkg_2d_gridded_tile_ancillary', NULL, "
    5862             :                 "'gpkg_2d_gridded_coverage', "
    5863             :                 "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5864             :                 "'read-write')"
    5865          40 :                 ";";
    5866             :     }
    5867             : 
    5868             :     // Requirement 6 /gpkg-extensions
    5869          40 :     char *pszSQL = sqlite3_mprintf(
    5870             :         "INSERT INTO gpkg_extensions "
    5871             :         "(table_name, column_name, extension_name, definition, scope) "
    5872             :         "VALUES ('%q', 'tile_data', "
    5873             :         "'gpkg_2d_gridded_coverage', "
    5874             :         "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5875             :         "'read-write')",
    5876             :         m_osRasterTable.c_str());
    5877          40 :     osSQL += pszSQL;
    5878          40 :     osSQL += ";";
    5879          40 :     sqlite3_free(pszSQL);
    5880             : 
    5881             :     // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
    5882             :     // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
    5883             :     // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
    5884          40 :     m_dfPrecision =
    5885          40 :         CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
    5886             :     CPLString osGridCellEncoding(CSLFetchNameValueDef(
    5887          80 :         papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
    5888          40 :     m_bGridCellEncodingAsCO =
    5889          40 :         CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
    5890          80 :     CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
    5891             :     CPLString osFieldName(
    5892          80 :         CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
    5893             :     CPLString osQuantityDefinition(
    5894          80 :         CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
    5895             : 
    5896         121 :     pszSQL = sqlite3_mprintf(
    5897             :         "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
    5898             :         "(tile_matrix_set_name, datatype, scale, offset, precision, "
    5899             :         "grid_cell_encoding, uom, field_name, quantity_definition) "
    5900             :         "VALUES (%Q, '%s', %.17g, %.17g, %.17g, %Q, %Q, %Q, %Q)",
    5901             :         m_osRasterTable.c_str(),
    5902          40 :         (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
    5903             :         m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
    5904          41 :         osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
    5905             :         osQuantityDefinition.c_str());
    5906          40 :     m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
    5907          40 :     sqlite3_free(pszSQL);
    5908             : 
    5909             :     // Requirement 3 /gpkg-spatial-ref-sys-row
    5910             :     auto oResultTable = SQLQuery(
    5911          80 :         hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
    5912          40 :     bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
    5913          40 :     if (!bHasEPSG4979)
    5914             :     {
    5915          41 :         if (!m_bHasDefinition12_063 &&
    5916           1 :             !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
    5917             :         {
    5918           0 :             return false;
    5919             :         }
    5920             : 
    5921             :         // This is WKT 2...
    5922          40 :         const char *pszWKT =
    5923             :             "GEODCRS[\"WGS 84\","
    5924             :             "DATUM[\"World Geodetic System 1984\","
    5925             :             "  ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
    5926             :             "LENGTHUNIT[\"metre\",1.0]]],"
    5927             :             "CS[ellipsoidal,3],"
    5928             :             "  AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
    5929             :             "0.0174532925199433]],"
    5930             :             "  AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
    5931             :             "0.0174532925199433]],"
    5932             :             "  AXIS[\"ellipsoidal height\",up,ORDER[3],"
    5933             :             "LENGTHUNIT[\"metre\",1.0]],"
    5934             :             "ID[\"EPSG\",4979]]";
    5935             : 
    5936          40 :         pszSQL = sqlite3_mprintf(
    5937             :             "INSERT INTO gpkg_spatial_ref_sys "
    5938             :             "(srs_name,srs_id,organization,organization_coordsys_id,"
    5939             :             "definition,definition_12_063) VALUES "
    5940             :             "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
    5941             :             pszWKT);
    5942          40 :         osSQL += ";";
    5943          40 :         osSQL += pszSQL;
    5944          40 :         sqlite3_free(pszSQL);
    5945             :     }
    5946             : 
    5947          40 :     return SQLCommand(hDB, osSQL) == OGRERR_NONE;
    5948             : }
    5949             : 
    5950             : /************************************************************************/
    5951             : /*                    HasGriddedCoverageAncillaryTable()                */
    5952             : /************************************************************************/
    5953             : 
    5954          44 : bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
    5955             : {
    5956             :     auto oResultTable = SQLQuery(
    5957             :         hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
    5958          44 :              "name = 'gpkg_2d_gridded_coverage_ancillary'");
    5959          44 :     bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
    5960          88 :     return bHasTable;
    5961             : }
    5962             : 
    5963             : /************************************************************************/
    5964             : /*                      GetUnderlyingDataset()                          */
    5965             : /************************************************************************/
    5966             : 
    5967           3 : static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
    5968             : {
    5969           3 :     if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
    5970             :     {
    5971           0 :         auto poTmpDS = poVRTDS->GetSingleSimpleSource();
    5972           0 :         if (poTmpDS)
    5973           0 :             return poTmpDS;
    5974             :     }
    5975             : 
    5976           3 :     return poSrcDS;
    5977             : }
    5978             : 
    5979             : /************************************************************************/
    5980             : /*                            CreateCopy()                              */
    5981             : /************************************************************************/
    5982             : 
    5983             : typedef struct
    5984             : {
    5985             :     const char *pszName;
    5986             :     GDALResampleAlg eResampleAlg;
    5987             : } WarpResamplingAlg;
    5988             : 
    5989             : static const WarpResamplingAlg asResamplingAlg[] = {
    5990             :     {"NEAREST", GRA_NearestNeighbour},
    5991             :     {"BILINEAR", GRA_Bilinear},
    5992             :     {"CUBIC", GRA_Cubic},
    5993             :     {"CUBICSPLINE", GRA_CubicSpline},
    5994             :     {"LANCZOS", GRA_Lanczos},
    5995             :     {"MODE", GRA_Mode},
    5996             :     {"AVERAGE", GRA_Average},
    5997             :     {"RMS", GRA_RMS},
    5998             : };
    5999             : 
    6000         160 : GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
    6001             :                                                GDALDataset *poSrcDS,
    6002             :                                                int bStrict, char **papszOptions,
    6003             :                                                GDALProgressFunc pfnProgress,
    6004             :                                                void *pProgressData)
    6005             : {
    6006         160 :     const int nBands = poSrcDS->GetRasterCount();
    6007         160 :     if (nBands == 0)
    6008             :     {
    6009           2 :         GDALDataset *poDS = nullptr;
    6010             :         GDALDriver *poThisDriver =
    6011           2 :             GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
    6012           2 :         if (poThisDriver != nullptr)
    6013             :         {
    6014           2 :             poDS = poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS,
    6015             :                                                    bStrict, papszOptions,
    6016             :                                                    pfnProgress, pProgressData);
    6017             :         }
    6018           2 :         return poDS;
    6019             :     }
    6020             : 
    6021             :     const char *pszTilingScheme =
    6022         158 :         CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
    6023             : 
    6024         316 :     CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
    6025         158 :     if (CPLTestBool(
    6026         164 :             CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
    6027           6 :         CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
    6028             :     {
    6029             :         const std::string osBasename(CPLGetBasenameSafe(
    6030           6 :             GetUnderlyingDataset(poSrcDS)->GetDescription()));
    6031           3 :         apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename.c_str());
    6032             :     }
    6033             : 
    6034         158 :     if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
    6035             :     {
    6036           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    6037             :                  "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
    6038             :                  "4 (RGBA) band dataset supported");
    6039           1 :         return nullptr;
    6040             :     }
    6041             : 
    6042         157 :     const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
    6043         314 :     if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
    6044         157 :         !EQUAL(pszUnitType, ""))
    6045             :     {
    6046           1 :         apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
    6047             :     }
    6048             : 
    6049         157 :     if (EQUAL(pszTilingScheme, "CUSTOM"))
    6050             :     {
    6051         133 :         if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
    6052             :         {
    6053           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    6054             :                      "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
    6055           0 :             return nullptr;
    6056             :         }
    6057             : 
    6058         133 :         GDALGeoPackageDataset *poDS = nullptr;
    6059             :         GDALDriver *poThisDriver =
    6060         133 :             GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
    6061         133 :         if (poThisDriver != nullptr)
    6062             :         {
    6063         133 :             apszUpdatedOptions.SetNameValue("SKIP_HOLES", "YES");
    6064         133 :             poDS = cpl::down_cast<GDALGeoPackageDataset *>(
    6065             :                 poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
    6066             :                                                 apszUpdatedOptions, pfnProgress,
    6067         133 :                                                 pProgressData));
    6068             : 
    6069         246 :             if (poDS != nullptr &&
    6070         133 :                 poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_Byte &&
    6071             :                 nBands <= 3)
    6072             :             {
    6073          73 :                 poDS->m_nBandCountFromMetadata = nBands;
    6074          73 :                 poDS->m_bMetadataDirty = true;
    6075             :             }
    6076             :         }
    6077         133 :         if (poDS)
    6078         113 :             poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    6079         133 :         return poDS;
    6080             :     }
    6081             : 
    6082          48 :     const auto poTS = GetTilingScheme(pszTilingScheme);
    6083          24 :     if (!poTS)
    6084             :     {
    6085           2 :         return nullptr;
    6086             :     }
    6087          22 :     const int nEPSGCode = poTS->nEPSGCode;
    6088             : 
    6089          44 :     OGRSpatialReference oSRS;
    6090          22 :     if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
    6091             :     {
    6092           0 :         return nullptr;
    6093             :     }
    6094          22 :     char *pszWKT = nullptr;
    6095          22 :     oSRS.exportToWkt(&pszWKT);
    6096          22 :     char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
    6097             : 
    6098          22 :     void *hTransformArg = nullptr;
    6099             : 
    6100             :     // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
    6101             :     // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
    6102             :     // EPSG:3857.
    6103             :     double adfSrcGeoTransform[6];
    6104          22 :     std::unique_ptr<GDALDataset> poTmpDS;
    6105          22 :     bool bEPSG3857Adjust = false;
    6106          30 :     if (nEPSGCode == 3857 &&
    6107           8 :         poSrcDS->GetGeoTransform(adfSrcGeoTransform) == CE_None &&
    6108          38 :         adfSrcGeoTransform[2] == 0 && adfSrcGeoTransform[4] == 0 &&
    6109           8 :         adfSrcGeoTransform[5] < 0)
    6110             :     {
    6111           8 :         const auto poSrcSRS = poSrcDS->GetSpatialRef();
    6112           8 :         if (poSrcSRS && poSrcSRS->IsGeographic())
    6113             :         {
    6114           2 :             double maxLat = adfSrcGeoTransform[3];
    6115           2 :             double minLat = adfSrcGeoTransform[3] +
    6116           2 :                             poSrcDS->GetRasterYSize() * adfSrcGeoTransform[5];
    6117             :             // Corresponds to the latitude of below MAX_GM
    6118           2 :             constexpr double MAX_LAT = 85.0511287798066;
    6119           2 :             bool bModified = false;
    6120           2 :             if (maxLat > MAX_LAT)
    6121             :             {
    6122           2 :                 maxLat = MAX_LAT;
    6123           2 :                 bModified = true;
    6124             :             }
    6125           2 :             if (minLat < -MAX_LAT)
    6126             :             {
    6127           2 :                 minLat = -MAX_LAT;
    6128           2 :                 bModified = true;
    6129             :             }
    6130           2 :             if (bModified)
    6131             :             {
    6132           4 :                 CPLStringList aosOptions;
    6133           2 :                 aosOptions.AddString("-of");
    6134           2 :                 aosOptions.AddString("VRT");
    6135           2 :                 aosOptions.AddString("-projwin");
    6136             :                 aosOptions.AddString(
    6137           2 :                     CPLSPrintf("%.17g", adfSrcGeoTransform[0]));
    6138           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
    6139             :                 aosOptions.AddString(
    6140           2 :                     CPLSPrintf("%.17g", adfSrcGeoTransform[0] +
    6141           2 :                                             poSrcDS->GetRasterXSize() *
    6142           2 :                                                 adfSrcGeoTransform[1]));
    6143           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", minLat));
    6144             :                 auto psOptions =
    6145           2 :                     GDALTranslateOptionsNew(aosOptions.List(), nullptr);
    6146           2 :                 poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
    6147             :                     "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
    6148           2 :                 GDALTranslateOptionsFree(psOptions);
    6149           2 :                 if (poTmpDS)
    6150             :                 {
    6151           2 :                     bEPSG3857Adjust = true;
    6152           2 :                     hTransformArg = GDALCreateGenImgProjTransformer2(
    6153           2 :                         GDALDataset::FromHandle(poTmpDS.get()), nullptr,
    6154             :                         papszTO);
    6155             :                 }
    6156             :             }
    6157             :         }
    6158             :     }
    6159          22 :     if (hTransformArg == nullptr)
    6160             :     {
    6161             :         hTransformArg =
    6162          20 :             GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
    6163             :     }
    6164             : 
    6165          22 :     if (hTransformArg == nullptr)
    6166             :     {
    6167           1 :         CPLFree(pszWKT);
    6168           1 :         CSLDestroy(papszTO);
    6169           1 :         return nullptr;
    6170             :     }
    6171             : 
    6172          21 :     GDALTransformerInfo *psInfo =
    6173             :         static_cast<GDALTransformerInfo *>(hTransformArg);
    6174             :     double adfGeoTransform[6];
    6175             :     double adfExtent[4];
    6176             :     int nXSize, nYSize;
    6177             : 
    6178          21 :     if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
    6179             :                                  adfGeoTransform, &nXSize, &nYSize, adfExtent,
    6180          21 :                                  0) != CE_None)
    6181             :     {
    6182           0 :         CPLFree(pszWKT);
    6183           0 :         CSLDestroy(papszTO);
    6184           0 :         GDALDestroyGenImgProjTransformer(hTransformArg);
    6185           0 :         return nullptr;
    6186             :     }
    6187             : 
    6188          21 :     GDALDestroyGenImgProjTransformer(hTransformArg);
    6189          21 :     hTransformArg = nullptr;
    6190          21 :     poTmpDS.reset();
    6191             : 
    6192          21 :     if (bEPSG3857Adjust)
    6193             :     {
    6194           2 :         constexpr double SPHERICAL_RADIUS = 6378137.0;
    6195           2 :         constexpr double MAX_GM =
    6196             :             SPHERICAL_RADIUS * M_PI;  // 20037508.342789244
    6197           2 :         double maxNorthing = adfGeoTransform[3];
    6198           2 :         double minNorthing = adfGeoTransform[3] + adfGeoTransform[5] * nYSize;
    6199           2 :         bool bChanged = false;
    6200           2 :         if (maxNorthing > MAX_GM)
    6201             :         {
    6202           2 :             bChanged = true;
    6203           2 :             maxNorthing = MAX_GM;
    6204             :         }
    6205           2 :         if (minNorthing < -MAX_GM)
    6206             :         {
    6207           2 :             bChanged = true;
    6208           2 :             minNorthing = -MAX_GM;
    6209             :         }
    6210           2 :         if (bChanged)
    6211             :         {
    6212           2 :             adfGeoTransform[3] = maxNorthing;
    6213           2 :             nYSize =
    6214           2 :                 int((maxNorthing - minNorthing) / (-adfGeoTransform[5]) + 0.5);
    6215           2 :             adfExtent[1] = maxNorthing + nYSize * adfGeoTransform[5];
    6216           2 :             adfExtent[3] = maxNorthing;
    6217             :         }
    6218             :     }
    6219             : 
    6220          21 :     double dfComputedRes = adfGeoTransform[1];
    6221          21 :     double dfPrevRes = 0.0;
    6222          21 :     double dfRes = 0.0;
    6223          21 :     int nZoomLevel = 0;  // Used after for.
    6224          21 :     const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
    6225          21 :     if (pszZoomLevel)
    6226             :     {
    6227           2 :         nZoomLevel = atoi(pszZoomLevel);
    6228             : 
    6229           2 :         int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
    6230           2 :         while ((1 << nMaxZoomLevelForThisTM) >
    6231           4 :                    INT_MAX / poTS->nTileXCountZoomLevel0 ||
    6232           2 :                (1 << nMaxZoomLevelForThisTM) >
    6233           2 :                    INT_MAX / poTS->nTileYCountZoomLevel0)
    6234             :         {
    6235           0 :             --nMaxZoomLevelForThisTM;
    6236             :         }
    6237             : 
    6238           2 :         if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
    6239             :         {
    6240           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6241             :                      "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
    6242             :                      pszZoomLevel, nMaxZoomLevelForThisTM);
    6243           1 :             CPLFree(pszWKT);
    6244           1 :             CSLDestroy(papszTO);
    6245           1 :             return nullptr;
    6246             :         }
    6247             :     }
    6248             :     else
    6249             :     {
    6250         171 :         for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
    6251             :         {
    6252         171 :             dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
    6253         171 :             if (dfComputedRes > dfRes ||
    6254         152 :                 fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
    6255             :                 break;
    6256         152 :             dfPrevRes = dfRes;
    6257             :         }
    6258          38 :         if (nZoomLevel == MAX_ZOOM_LEVEL ||
    6259          38 :             (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
    6260          19 :             (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
    6261             :         {
    6262           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6263             :                      "Could not find an appropriate zoom level");
    6264           0 :             CPLFree(pszWKT);
    6265           0 :             CSLDestroy(papszTO);
    6266           0 :             return nullptr;
    6267             :         }
    6268             : 
    6269          19 :         if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
    6270             :         {
    6271          17 :             const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
    6272             :                 papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
    6273          17 :             if (EQUAL(pszZoomLevelStrategy, "LOWER"))
    6274             :             {
    6275           1 :                 nZoomLevel--;
    6276             :             }
    6277          16 :             else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
    6278             :             {
    6279             :                 /* do nothing */
    6280             :             }
    6281             :             else
    6282             :             {
    6283          15 :                 if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
    6284          13 :                     nZoomLevel--;
    6285             :             }
    6286             :         }
    6287             :     }
    6288             : 
    6289          20 :     dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
    6290             : 
    6291          20 :     double dfMinX = adfExtent[0];
    6292          20 :     double dfMinY = adfExtent[1];
    6293          20 :     double dfMaxX = adfExtent[2];
    6294          20 :     double dfMaxY = adfExtent[3];
    6295             : 
    6296          20 :     nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
    6297          20 :     nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
    6298          20 :     adfGeoTransform[1] = dfRes;
    6299          20 :     adfGeoTransform[5] = -dfRes;
    6300             : 
    6301          20 :     const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
    6302          20 :     int nTargetBands = nBands;
    6303             :     /* For grey level or RGB, if there's reprojection involved, add an alpha */
    6304             :     /* channel */
    6305          37 :     if (eDT == GDT_Byte &&
    6306          13 :         ((nBands == 1 &&
    6307          17 :           poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
    6308             :          nBands == 3))
    6309             :     {
    6310          30 :         OGRSpatialReference oSrcSRS;
    6311          15 :         oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
    6312          15 :         oSrcSRS.AutoIdentifyEPSG();
    6313          30 :         if (oSrcSRS.GetAuthorityCode(nullptr) == nullptr ||
    6314          15 :             atoi(oSrcSRS.GetAuthorityCode(nullptr)) != nEPSGCode)
    6315             :         {
    6316          13 :             nTargetBands++;
    6317             :         }
    6318             :     }
    6319             : 
    6320          20 :     GDALResampleAlg eResampleAlg = GRA_Bilinear;
    6321          20 :     const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
    6322          20 :     if (pszResampling)
    6323             :     {
    6324           6 :         for (size_t iAlg = 0;
    6325           6 :              iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
    6326             :              iAlg++)
    6327             :         {
    6328           6 :             if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
    6329             :             {
    6330           3 :                 eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
    6331           3 :                 break;
    6332             :             }
    6333             :         }
    6334             :     }
    6335             : 
    6336          16 :     if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
    6337          36 :         eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
    6338             :     {
    6339           0 :         CPLError(
    6340             :             CE_Warning, CPLE_AppDefined,
    6341             :             "Input dataset has a color table, which will likely lead to "
    6342             :             "bad results when using a resampling method other than "
    6343             :             "nearest neighbour or mode. Converting the dataset to 24/32 bit "
    6344             :             "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
    6345             :     }
    6346             : 
    6347          20 :     GDALGeoPackageDataset *poDS = new GDALGeoPackageDataset();
    6348          20 :     if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
    6349             :                        apszUpdatedOptions)))
    6350             :     {
    6351           1 :         delete poDS;
    6352           1 :         CPLFree(pszWKT);
    6353           1 :         CSLDestroy(papszTO);
    6354           1 :         return nullptr;
    6355             :     }
    6356             : 
    6357             :     // Assign nodata values before the SetGeoTransform call.
    6358             :     // SetGeoTransform will trigger creation of the overview datasets for each
    6359             :     // zoom level and at that point the nodata value needs to be known.
    6360          19 :     int bHasNoData = FALSE;
    6361             :     double dfNoDataValue =
    6362          19 :         poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    6363          19 :     if (eDT != GDT_Byte && bHasNoData)
    6364             :     {
    6365           3 :         poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
    6366             :     }
    6367             : 
    6368          19 :     poDS->SetGeoTransform(adfGeoTransform);
    6369          19 :     poDS->SetProjection(pszWKT);
    6370          19 :     CPLFree(pszWKT);
    6371          19 :     pszWKT = nullptr;
    6372          24 :     if (nTargetBands == 1 && nBands == 1 &&
    6373           5 :         poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
    6374             :     {
    6375           2 :         poDS->GetRasterBand(1)->SetColorTable(
    6376           1 :             poSrcDS->GetRasterBand(1)->GetColorTable());
    6377             :     }
    6378             : 
    6379          19 :     hTransformArg = GDALCreateGenImgProjTransformer2(poSrcDS, poDS, papszTO);
    6380          19 :     CSLDestroy(papszTO);
    6381          19 :     if (hTransformArg == nullptr)
    6382             :     {
    6383           0 :         delete poDS;
    6384           0 :         return nullptr;
    6385             :     }
    6386             : 
    6387          19 :     poDS->SetMetadata(poSrcDS->GetMetadata());
    6388             : 
    6389             :     /* -------------------------------------------------------------------- */
    6390             :     /*      Warp the transformer with a linear approximator                 */
    6391             :     /* -------------------------------------------------------------------- */
    6392          19 :     hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
    6393             :                                                 hTransformArg, 0.125);
    6394          19 :     GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
    6395             : 
    6396             :     /* -------------------------------------------------------------------- */
    6397             :     /*      Setup warp options.                                             */
    6398             :     /* -------------------------------------------------------------------- */
    6399          19 :     GDALWarpOptions *psWO = GDALCreateWarpOptions();
    6400             : 
    6401          19 :     psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
    6402          19 :     psWO->papszWarpOptions =
    6403          19 :         CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
    6404          19 :     if (bHasNoData)
    6405             :     {
    6406           3 :         if (dfNoDataValue == 0.0)
    6407             :         {
    6408             :             // Do not initialize in the case where nodata != 0, since we
    6409             :             // want the GeoPackage driver to return empty tiles at the nodata
    6410             :             // value instead of 0 as GDAL core would
    6411           0 :             psWO->papszWarpOptions =
    6412           0 :                 CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
    6413             :         }
    6414             : 
    6415           3 :         psWO->padfSrcNoDataReal =
    6416           3 :             static_cast<double *>(CPLMalloc(sizeof(double)));
    6417           3 :         psWO->padfSrcNoDataReal[0] = dfNoDataValue;
    6418             : 
    6419           3 :         psWO->padfDstNoDataReal =
    6420           3 :             static_cast<double *>(CPLMalloc(sizeof(double)));
    6421           3 :         psWO->padfDstNoDataReal[0] = dfNoDataValue;
    6422             :     }
    6423          19 :     psWO->eWorkingDataType = eDT;
    6424          19 :     psWO->eResampleAlg = eResampleAlg;
    6425             : 
    6426          19 :     psWO->hSrcDS = poSrcDS;
    6427          19 :     psWO->hDstDS = poDS;
    6428             : 
    6429          19 :     psWO->pfnTransformer = GDALApproxTransform;
    6430          19 :     psWO->pTransformerArg = hTransformArg;
    6431             : 
    6432          19 :     psWO->pfnProgress = pfnProgress;
    6433          19 :     psWO->pProgressArg = pProgressData;
    6434             : 
    6435             :     /* -------------------------------------------------------------------- */
    6436             :     /*      Setup band mapping.                                             */
    6437             :     /* -------------------------------------------------------------------- */
    6438             : 
    6439          19 :     if (nBands == 2 || nBands == 4)
    6440           1 :         psWO->nBandCount = nBands - 1;
    6441             :     else
    6442          18 :         psWO->nBandCount = nBands;
    6443             : 
    6444          19 :     psWO->panSrcBands =
    6445          19 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    6446          19 :     psWO->panDstBands =
    6447          19 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    6448             : 
    6449          46 :     for (int i = 0; i < psWO->nBandCount; i++)
    6450             :     {
    6451          27 :         psWO->panSrcBands[i] = i + 1;
    6452          27 :         psWO->panDstBands[i] = i + 1;
    6453             :     }
    6454             : 
    6455          19 :     if (nBands == 2 || nBands == 4)
    6456             :     {
    6457           1 :         psWO->nSrcAlphaBand = nBands;
    6458             :     }
    6459          19 :     if (nTargetBands == 2 || nTargetBands == 4)
    6460             :     {
    6461          13 :         psWO->nDstAlphaBand = nTargetBands;
    6462             :     }
    6463             : 
    6464             :     /* -------------------------------------------------------------------- */
    6465             :     /*      Initialize and execute the warp.                                */
    6466             :     /* -------------------------------------------------------------------- */
    6467          19 :     GDALWarpOperation oWO;
    6468             : 
    6469          19 :     CPLErr eErr = oWO.Initialize(psWO);
    6470          19 :     if (eErr == CE_None)
    6471             :     {
    6472             :         /*if( bMulti )
    6473             :             eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
    6474             :         else*/
    6475          19 :         eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
    6476             :     }
    6477          19 :     if (eErr != CE_None)
    6478             :     {
    6479           0 :         delete poDS;
    6480           0 :         poDS = nullptr;
    6481             :     }
    6482             : 
    6483          19 :     GDALDestroyTransformer(hTransformArg);
    6484          19 :     GDALDestroyWarpOptions(psWO);
    6485             : 
    6486          19 :     if (poDS)
    6487          19 :         poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    6488             : 
    6489          19 :     return poDS;
    6490             : }
    6491             : 
    6492             : /************************************************************************/
    6493             : /*                        ParseCompressionOptions()                     */
    6494             : /************************************************************************/
    6495             : 
    6496         451 : void GDALGeoPackageDataset::ParseCompressionOptions(char **papszOptions)
    6497             : {
    6498         451 :     const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
    6499         451 :     if (pszZLevel)
    6500           0 :         m_nZLevel = atoi(pszZLevel);
    6501             : 
    6502         451 :     const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
    6503         451 :     if (pszQuality)
    6504           0 :         m_nQuality = atoi(pszQuality);
    6505             : 
    6506         451 :     const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
    6507         451 :     if (pszDither)
    6508           0 :         m_bDither = CPLTestBool(pszDither);
    6509         451 : }
    6510             : 
    6511             : /************************************************************************/
    6512             : /*                          RegisterWebPExtension()                     */
    6513             : /************************************************************************/
    6514             : 
    6515          11 : bool GDALGeoPackageDataset::RegisterWebPExtension()
    6516             : {
    6517          11 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    6518           0 :         return false;
    6519             : 
    6520          11 :     char *pszSQL = sqlite3_mprintf(
    6521             :         "INSERT INTO gpkg_extensions "
    6522             :         "(table_name, column_name, extension_name, definition, scope) "
    6523             :         "VALUES "
    6524             :         "('%q', 'tile_data', 'gpkg_webp', "
    6525             :         "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
    6526             :         "'read-write')",
    6527             :         m_osRasterTable.c_str());
    6528          11 :     const OGRErr eErr = SQLCommand(hDB, pszSQL);
    6529          11 :     sqlite3_free(pszSQL);
    6530             : 
    6531          11 :     return OGRERR_NONE == eErr;
    6532             : }
    6533             : 
    6534             : /************************************************************************/
    6535             : /*                       RegisterZoomOtherExtension()                   */
    6536             : /************************************************************************/
    6537             : 
    6538           1 : bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
    6539             : {
    6540           1 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    6541           0 :         return false;
    6542             : 
    6543           1 :     char *pszSQL = sqlite3_mprintf(
    6544             :         "INSERT INTO gpkg_extensions "
    6545             :         "(table_name, column_name, extension_name, definition, scope) "
    6546             :         "VALUES "
    6547             :         "('%q', 'tile_data', 'gpkg_zoom_other', "
    6548             :         "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
    6549             :         "'read-write')",
    6550             :         m_osRasterTable.c_str());
    6551           1 :     const OGRErr eErr = SQLCommand(hDB, pszSQL);
    6552           1 :     sqlite3_free(pszSQL);
    6553           1 :     return OGRERR_NONE == eErr;
    6554             : }
    6555             : 
    6556             : /************************************************************************/
    6557             : /*                              GetLayer()                              */
    6558             : /************************************************************************/
    6559             : 
    6560       15122 : OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer)
    6561             : 
    6562             : {
    6563       15122 :     if (iLayer < 0 || iLayer >= m_nLayers)
    6564           6 :         return nullptr;
    6565             :     else
    6566       15116 :         return m_papoLayers[iLayer];
    6567             : }
    6568             : 
    6569             : /************************************************************************/
    6570             : /*                           LaunderName()                              */
    6571             : /************************************************************************/
    6572             : 
    6573             : /** Launder identifiers (table, column names) according to guidance at
    6574             :  * https://www.geopackage.org/guidance/getting-started.html:
    6575             :  * "For maximum interoperability, start your database identifiers (table names,
    6576             :  * column names, etc.) with a lowercase character and only use lowercase
    6577             :  * characters, numbers 0-9, and underscores (_)."
    6578             :  */
    6579             : 
    6580             : /* static */
    6581           5 : std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
    6582             : {
    6583           5 :     char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
    6584          10 :     const std::string osStrASCII(pszASCII);
    6585           5 :     CPLFree(pszASCII);
    6586             : 
    6587          10 :     std::string osRet;
    6588           5 :     osRet.reserve(osStrASCII.size());
    6589             : 
    6590          29 :     for (size_t i = 0; i < osStrASCII.size(); ++i)
    6591             :     {
    6592          24 :         if (osRet.empty())
    6593             :         {
    6594           5 :             if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
    6595             :             {
    6596           2 :                 osRet += (osStrASCII[i] - 'A' + 'a');
    6597             :             }
    6598           3 :             else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
    6599             :             {
    6600           2 :                 osRet += osStrASCII[i];
    6601             :             }
    6602             :             else
    6603             :             {
    6604           1 :                 continue;
    6605             :             }
    6606             :         }
    6607          19 :         else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
    6608             :         {
    6609          11 :             osRet += (osStrASCII[i] - 'A' + 'a');
    6610             :         }
    6611           9 :         else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
    6612          14 :                  (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
    6613           5 :                  osStrASCII[i] == '_')
    6614             :         {
    6615           7 :             osRet += osStrASCII[i];
    6616             :         }
    6617             :         else
    6618             :         {
    6619           1 :             osRet += '_';
    6620             :         }
    6621             :     }
    6622             : 
    6623           5 :     if (osRet.empty() && !osStrASCII.empty())
    6624           2 :         return LaunderName(std::string("x").append(osStrASCII));
    6625             : 
    6626           4 :     if (osRet != osStr)
    6627             :     {
    6628           3 :         CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
    6629             :                  osRet.c_str());
    6630             :     }
    6631             : 
    6632           4 :     return osRet;
    6633             : }
    6634             : 
    6635             : /************************************************************************/
    6636             : /*                          ICreateLayer()                              */
    6637             : /************************************************************************/
    6638             : 
    6639             : OGRLayer *
    6640         727 : GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
    6641             :                                     const OGRGeomFieldDefn *poSrcGeomFieldDefn,
    6642             :                                     CSLConstList papszOptions)
    6643             : {
    6644             :     /* -------------------------------------------------------------------- */
    6645             :     /*      Verify we are in update mode.                                   */
    6646             :     /* -------------------------------------------------------------------- */
    6647         727 :     if (!GetUpdate())
    6648             :     {
    6649           0 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
    6650             :                  "Data source %s opened read-only.\n"
    6651             :                  "New layer %s cannot be created.\n",
    6652             :                  m_pszFilename, pszLayerName);
    6653             : 
    6654           0 :         return nullptr;
    6655             :     }
    6656             : 
    6657             :     const bool bLaunder =
    6658         727 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
    6659             :     const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
    6660        2181 :                                            : std::string(pszLayerName));
    6661             : 
    6662             :     const auto eGType =
    6663         727 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
    6664             :     const auto poSpatialRef =
    6665         727 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
    6666             : 
    6667         727 :     if (!m_bHasGPKGGeometryColumns)
    6668             :     {
    6669           1 :         if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
    6670             :         {
    6671           0 :             return nullptr;
    6672             :         }
    6673           1 :         m_bHasGPKGGeometryColumns = true;
    6674             :     }
    6675             : 
    6676             :     // Check identifier unicity
    6677         727 :     const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
    6678         727 :     if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
    6679           0 :         pszIdentifier = nullptr;
    6680         727 :     if (pszIdentifier != nullptr)
    6681             :     {
    6682          13 :         for (int i = 0; i < m_nLayers; ++i)
    6683             :         {
    6684             :             const char *pszOtherIdentifier =
    6685           9 :                 m_papoLayers[i]->GetMetadataItem("IDENTIFIER");
    6686           9 :             if (pszOtherIdentifier == nullptr)
    6687           6 :                 pszOtherIdentifier = m_papoLayers[i]->GetName();
    6688          18 :             if (pszOtherIdentifier != nullptr &&
    6689          12 :                 EQUAL(pszOtherIdentifier, pszIdentifier) &&
    6690           3 :                 !EQUAL(m_papoLayers[i]->GetName(), osTableName.c_str()))
    6691             :             {
    6692           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    6693             :                          "Identifier %s is already used by table %s",
    6694           2 :                          pszIdentifier, m_papoLayers[i]->GetName());
    6695           3 :                 return nullptr;
    6696             :             }
    6697             :         }
    6698             : 
    6699             :         // In case there would be table in gpkg_contents not listed as a
    6700             :         // vector layer
    6701           4 :         char *pszSQL = sqlite3_mprintf(
    6702             :             "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
    6703             :             "LIMIT 2",
    6704             :             pszIdentifier);
    6705           4 :         auto oResult = SQLQuery(hDB, pszSQL);
    6706           4 :         sqlite3_free(pszSQL);
    6707           8 :         if (oResult && oResult->RowCount() > 0 &&
    6708           9 :             oResult->GetValue(0, 0) != nullptr &&
    6709           1 :             !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
    6710             :         {
    6711           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6712             :                      "Identifier %s is already used by table %s", pszIdentifier,
    6713             :                      oResult->GetValue(0, 0));
    6714           1 :             return nullptr;
    6715             :         }
    6716             :     }
    6717             : 
    6718             :     /* Read GEOMETRY_NAME option */
    6719             :     const char *pszGeomColumnName =
    6720         724 :         CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
    6721         724 :     if (pszGeomColumnName == nullptr) /* deprecated name */
    6722         644 :         pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
    6723         724 :     if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
    6724             :     {
    6725         592 :         pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
    6726         592 :         if (pszGeomColumnName && pszGeomColumnName[0] == 0)
    6727         588 :             pszGeomColumnName = nullptr;
    6728             :     }
    6729         724 :     if (pszGeomColumnName == nullptr)
    6730         640 :         pszGeomColumnName = "geom";
    6731             :     const bool bGeomNullable =
    6732         724 :         CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
    6733             : 
    6734             :     /* Read FID option */
    6735         724 :     const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
    6736         724 :     if (pszFIDColumnName == nullptr)
    6737         690 :         pszFIDColumnName = "fid";
    6738             : 
    6739         724 :     if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
    6740             :     {
    6741         724 :         if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
    6742             :         {
    6743           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6744             :                      "The primary key (%s) name may not contain special "
    6745             :                      "characters or spaces",
    6746             :                      pszFIDColumnName);
    6747           0 :             return nullptr;
    6748             :         }
    6749             : 
    6750             :         /* Avoiding gpkg prefixes is not an official requirement, but seems wise
    6751             :          */
    6752         724 :         if (STARTS_WITH(osTableName.c_str(), "gpkg"))
    6753             :         {
    6754           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6755             :                      "The layer name may not begin with 'gpkg' as it is a "
    6756             :                      "reserved geopackage prefix");
    6757           0 :             return nullptr;
    6758             :         }
    6759             : 
    6760             :         /* Preemptively try and avoid sqlite3 syntax errors due to  */
    6761             :         /* illegal characters. */
    6762         724 :         if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
    6763             :             0)
    6764             :         {
    6765           0 :             CPLError(
    6766             :                 CE_Failure, CPLE_AppDefined,
    6767             :                 "The layer name may not contain special characters or spaces");
    6768           0 :             return nullptr;
    6769             :         }
    6770             :     }
    6771             : 
    6772             :     /* Check for any existing layers that already use this name */
    6773         925 :     for (int iLayer = 0; iLayer < m_nLayers; iLayer++)
    6774             :     {
    6775         202 :         if (EQUAL(osTableName.c_str(), m_papoLayers[iLayer]->GetName()))
    6776             :         {
    6777             :             const char *pszOverwrite =
    6778           2 :                 CSLFetchNameValue(papszOptions, "OVERWRITE");
    6779           2 :             if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
    6780             :             {
    6781           1 :                 DeleteLayer(iLayer);
    6782             :             }
    6783             :             else
    6784             :             {
    6785           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    6786             :                          "Layer %s already exists, CreateLayer failed.\n"
    6787             :                          "Use the layer creation option OVERWRITE=YES to "
    6788             :                          "replace it.",
    6789             :                          osTableName.c_str());
    6790           1 :                 return nullptr;
    6791             :             }
    6792             :         }
    6793             :     }
    6794             : 
    6795         723 :     if (m_nLayers == 1)
    6796             :     {
    6797             :         // Async RTree building doesn't play well with multiple layer:
    6798             :         // SQLite3 locks being hold for a long time, random failed commits,
    6799             :         // etc.
    6800          75 :         m_papoLayers[0]->FinishOrDisableThreadedRTree();
    6801             :     }
    6802             : 
    6803             :     /* Create a blank layer. */
    6804             :     auto poLayer = std::unique_ptr<OGRGeoPackageTableLayer>(
    6805        1446 :         new OGRGeoPackageTableLayer(this, osTableName.c_str()));
    6806             : 
    6807         723 :     OGRSpatialReference *poSRS = nullptr;
    6808         723 :     if (poSpatialRef)
    6809             :     {
    6810         221 :         poSRS = poSpatialRef->Clone();
    6811         221 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6812             :     }
    6813        1447 :     poLayer->SetCreationParameters(
    6814             :         eGType,
    6815         724 :         bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
    6816             :         bGeomNullable, poSRS, CSLFetchNameValue(papszOptions, "SRID"),
    6817        1446 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
    6818             :                            : OGRGeomCoordinatePrecision(),
    6819         723 :         CPLTestBool(
    6820             :             CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
    6821         723 :         CPLTestBool(CSLFetchNameValueDef(
    6822             :             papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
    6823         724 :         bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
    6824             :         pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
    6825         723 :     if (poSRS)
    6826             :     {
    6827         221 :         poSRS->Release();
    6828             :     }
    6829             : 
    6830         723 :     poLayer->SetLaunder(bLaunder);
    6831             : 
    6832             :     /* Should we create a spatial index ? */
    6833         723 :     const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
    6834         723 :     int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
    6835         723 :     if (eGType != wkbNone && bCreateSpatialIndex)
    6836             :     {
    6837         649 :         poLayer->SetDeferredSpatialIndexCreation(true);
    6838             :     }
    6839             : 
    6840         723 :     poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
    6841         723 :     poLayer->SetTruncateFieldsFlag(
    6842         723 :         CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
    6843         723 :     if (eGType == wkbNone)
    6844             :     {
    6845          52 :         const char *pszASpatialVariant = CSLFetchNameValueDef(
    6846             :             papszOptions, "ASPATIAL_VARIANT",
    6847          52 :             m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
    6848             :                 ? "NOT_REGISTERED"
    6849             :                 : "GPKG_ATTRIBUTES");
    6850          52 :         GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
    6851          52 :         if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
    6852          40 :             eASpatialVariant = GPKG_ATTRIBUTES;
    6853          12 :         else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
    6854             :         {
    6855           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    6856             :                      "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
    6857           0 :             return nullptr;
    6858             :         }
    6859          12 :         else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
    6860          12 :             eASpatialVariant = NOT_REGISTERED;
    6861             :         else
    6862             :         {
    6863           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    6864             :                      "Unsupported value for ASPATIAL_VARIANT: %s",
    6865             :                      pszASpatialVariant);
    6866           0 :             return nullptr;
    6867             :         }
    6868          52 :         poLayer->SetASpatialVariant(eASpatialVariant);
    6869             :     }
    6870             : 
    6871             :     const char *pszDateTimePrecision =
    6872         723 :         CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
    6873         723 :     if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
    6874             :     {
    6875           2 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
    6876             :     }
    6877         721 :     else if (EQUAL(pszDateTimePrecision, "SECOND"))
    6878             :     {
    6879           1 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6880           0 :             CPLError(
    6881             :                 CE_Warning, CPLE_AppDefined,
    6882             :                 "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
    6883           1 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
    6884             :     }
    6885         720 :     else if (EQUAL(pszDateTimePrecision, "MINUTE"))
    6886             :     {
    6887           1 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6888           0 :             CPLError(
    6889             :                 CE_Warning, CPLE_AppDefined,
    6890             :                 "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
    6891           1 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
    6892             :     }
    6893         719 :     else if (EQUAL(pszDateTimePrecision, "AUTO"))
    6894             :     {
    6895         718 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6896         707 :             poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
    6897             :     }
    6898             :     else
    6899             :     {
    6900           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    6901             :                  "Unsupported value for DATETIME_PRECISION: %s",
    6902             :                  pszDateTimePrecision);
    6903           1 :         return nullptr;
    6904             :     }
    6905             : 
    6906             :     // If there was an ogr_empty_table table, we can remove it
    6907             :     // But do it at dataset closing, otherwise locking performance issues
    6908             :     // can arise (probably when transactions are used).
    6909         722 :     m_bRemoveOGREmptyTable = true;
    6910             : 
    6911        1444 :     m_papoLayers = static_cast<OGRGeoPackageTableLayer **>(CPLRealloc(
    6912         722 :         m_papoLayers, sizeof(OGRGeoPackageTableLayer *) * (m_nLayers + 1)));
    6913         722 :     auto poRet = poLayer.release();
    6914         722 :     m_papoLayers[m_nLayers] = poRet;
    6915         722 :     m_nLayers++;
    6916         722 :     return poRet;
    6917             : }
    6918             : 
    6919             : /************************************************************************/
    6920             : /*                          FindLayerIndex()                            */
    6921             : /************************************************************************/
    6922             : 
    6923          27 : int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
    6924             : 
    6925             : {
    6926          42 :     for (int iLayer = 0; iLayer < m_nLayers; iLayer++)
    6927             :     {
    6928          28 :         if (EQUAL(pszLayerName, m_papoLayers[iLayer]->GetName()))
    6929          13 :             return iLayer;
    6930             :     }
    6931          14 :     return -1;
    6932             : }
    6933             : 
    6934             : /************************************************************************/
    6935             : /*                       DeleteLayerCommon()                            */
    6936             : /************************************************************************/
    6937             : 
    6938          39 : OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
    6939             : {
    6940             :     // Temporary remove foreign key checks
    6941             :     const GPKGTemporaryForeignKeyCheckDisabler
    6942          39 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    6943             : 
    6944          39 :     char *pszSQL = sqlite3_mprintf(
    6945             :         "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
    6946             :         pszLayerName);
    6947          39 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
    6948          39 :     sqlite3_free(pszSQL);
    6949             : 
    6950          39 :     if (eErr == OGRERR_NONE && HasExtensionsTable())
    6951             :     {
    6952          37 :         pszSQL = sqlite3_mprintf(
    6953             :             "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
    6954             :             pszLayerName);
    6955          37 :         eErr = SQLCommand(hDB, pszSQL);
    6956          37 :         sqlite3_free(pszSQL);
    6957             :     }
    6958             : 
    6959          39 :     if (eErr == OGRERR_NONE && HasMetadataTables())
    6960             :     {
    6961             :         // Delete from gpkg_metadata metadata records that are only referenced
    6962             :         // by the table we are about to drop
    6963          10 :         pszSQL = sqlite3_mprintf(
    6964             :             "DELETE FROM gpkg_metadata WHERE id IN ("
    6965             :             "SELECT DISTINCT md_file_id FROM "
    6966             :             "gpkg_metadata_reference WHERE "
    6967             :             "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
    6968             :             "AND id NOT IN ("
    6969             :             "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
    6970             :             "md_file_id IN (SELECT DISTINCT md_file_id FROM "
    6971             :             "gpkg_metadata_reference WHERE "
    6972             :             "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
    6973             :             "AND lower(table_name) <> lower('%q'))",
    6974             :             pszLayerName, pszLayerName, pszLayerName);
    6975          10 :         eErr = SQLCommand(hDB, pszSQL);
    6976          10 :         sqlite3_free(pszSQL);
    6977             : 
    6978          10 :         if (eErr == OGRERR_NONE)
    6979             :         {
    6980             :             pszSQL =
    6981          10 :                 sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
    6982             :                                 "lower(table_name) = lower('%q')",
    6983             :                                 pszLayerName);
    6984          10 :             eErr = SQLCommand(hDB, pszSQL);
    6985          10 :             sqlite3_free(pszSQL);
    6986             :         }
    6987             :     }
    6988             : 
    6989          39 :     if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
    6990             :     {
    6991             :         // Remove reference to potential corresponding mapping table in
    6992             :         // gpkg_extensions
    6993           4 :         pszSQL = sqlite3_mprintf(
    6994             :             "DELETE FROM gpkg_extensions WHERE "
    6995             :             "extension_name IN ('related_tables', "
    6996             :             "'gpkg_related_tables') AND lower(table_name) = "
    6997             :             "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
    6998             :             "lower(base_table_name) = lower('%q') OR "
    6999             :             "lower(related_table_name) = lower('%q') OR "
    7000             :             "lower(mapping_table_name) = lower('%q'))",
    7001             :             pszLayerName, pszLayerName, pszLayerName);
    7002           4 :         eErr = SQLCommand(hDB, pszSQL);
    7003           4 :         sqlite3_free(pszSQL);
    7004             : 
    7005           4 :         if (eErr == OGRERR_NONE)
    7006             :         {
    7007             :             // Remove reference to potential corresponding mapping table in
    7008             :             // gpkgext_relations
    7009             :             pszSQL =
    7010           4 :                 sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
    7011             :                                 "lower(base_table_name) = lower('%q') OR "
    7012             :                                 "lower(related_table_name) = lower('%q') OR "
    7013             :                                 "lower(mapping_table_name) = lower('%q')",
    7014             :                                 pszLayerName, pszLayerName, pszLayerName);
    7015           4 :             eErr = SQLCommand(hDB, pszSQL);
    7016           4 :             sqlite3_free(pszSQL);
    7017             :         }
    7018             : 
    7019           4 :         if (eErr == OGRERR_NONE && HasExtensionsTable())
    7020             :         {
    7021             :             // If there is no longer any mapping table, then completely
    7022             :             // remove any reference to the extension in gpkg_extensions
    7023             :             // as mandated per the related table specification.
    7024             :             OGRErr err;
    7025           4 :             if (SQLGetInteger(hDB,
    7026             :                               "SELECT COUNT(*) FROM gpkg_extensions WHERE "
    7027             :                               "extension_name IN ('related_tables', "
    7028             :                               "'gpkg_related_tables') AND "
    7029             :                               "lower(table_name) != 'gpkgext_relations'",
    7030           4 :                               &err) == 0)
    7031             :             {
    7032           2 :                 eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
    7033             :                                        "extension_name IN ('related_tables', "
    7034             :                                        "'gpkg_related_tables')");
    7035             :             }
    7036             : 
    7037           4 :             ClearCachedRelationships();
    7038             :         }
    7039             :     }
    7040             : 
    7041          39 :     if (eErr == OGRERR_NONE)
    7042             :     {
    7043          39 :         pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
    7044          39 :         eErr = SQLCommand(hDB, pszSQL);
    7045          39 :         sqlite3_free(pszSQL);
    7046             :     }
    7047             : 
    7048             :     // Check foreign key integrity
    7049          39 :     if (eErr == OGRERR_NONE)
    7050             :     {
    7051          39 :         eErr = PragmaCheck("foreign_key_check", "", 0);
    7052             :     }
    7053             : 
    7054          78 :     return eErr;
    7055             : }
    7056             : 
    7057             : /************************************************************************/
    7058             : /*                            DeleteLayer()                             */
    7059             : /************************************************************************/
    7060             : 
    7061          36 : OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
    7062             : {
    7063          36 :     if (!GetUpdate() || iLayer < 0 || iLayer >= m_nLayers)
    7064           2 :         return OGRERR_FAILURE;
    7065             : 
    7066          34 :     m_papoLayers[iLayer]->ResetReading();
    7067          34 :     m_papoLayers[iLayer]->SyncToDisk();
    7068             : 
    7069          68 :     CPLString osLayerName = m_papoLayers[iLayer]->GetName();
    7070             : 
    7071          34 :     CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
    7072             : 
    7073             :     // Temporary remove foreign key checks
    7074             :     const GPKGTemporaryForeignKeyCheckDisabler
    7075          34 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7076             : 
    7077          34 :     OGRErr eErr = SoftStartTransaction();
    7078             : 
    7079          34 :     if (eErr == OGRERR_NONE)
    7080             :     {
    7081          34 :         if (m_papoLayers[iLayer]->HasSpatialIndex())
    7082          31 :             m_papoLayers[iLayer]->DropSpatialIndex();
    7083             : 
    7084             :         char *pszSQL =
    7085          34 :             sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
    7086             :                             "lower(table_name) = lower('%q')",
    7087             :                             osLayerName.c_str());
    7088          34 :         eErr = SQLCommand(hDB, pszSQL);
    7089          34 :         sqlite3_free(pszSQL);
    7090             :     }
    7091             : 
    7092          34 :     if (eErr == OGRERR_NONE && HasDataColumnsTable())
    7093             :     {
    7094           1 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
    7095             :                                        "lower(table_name) = lower('%q')",
    7096             :                                        osLayerName.c_str());
    7097           1 :         eErr = SQLCommand(hDB, pszSQL);
    7098           1 :         sqlite3_free(pszSQL);
    7099             :     }
    7100             : 
    7101             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7102          34 :     if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
    7103             :     {
    7104          34 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
    7105             :                                        "lower(table_name) = lower('%q')",
    7106             :                                        osLayerName.c_str());
    7107          34 :         eErr = SQLCommand(hDB, pszSQL);
    7108          34 :         sqlite3_free(pszSQL);
    7109             :     }
    7110             : #endif
    7111             : 
    7112          34 :     if (eErr == OGRERR_NONE)
    7113             :     {
    7114          34 :         eErr = DeleteLayerCommon(osLayerName.c_str());
    7115             :     }
    7116             : 
    7117          34 :     if (eErr == OGRERR_NONE)
    7118             :     {
    7119          34 :         eErr = SoftCommitTransaction();
    7120          34 :         if (eErr == OGRERR_NONE)
    7121             :         {
    7122             :             /* Delete the layer object and remove the gap in the layers list */
    7123          34 :             delete m_papoLayers[iLayer];
    7124          34 :             memmove(m_papoLayers + iLayer, m_papoLayers + iLayer + 1,
    7125          34 :                     sizeof(void *) * (m_nLayers - iLayer - 1));
    7126          34 :             m_nLayers--;
    7127             :         }
    7128             :     }
    7129             :     else
    7130             :     {
    7131           0 :         SoftRollbackTransaction();
    7132             :     }
    7133             : 
    7134          34 :     return eErr;
    7135             : }
    7136             : 
    7137             : /************************************************************************/
    7138             : /*                       DeleteRasterLayer()                            */
    7139             : /************************************************************************/
    7140             : 
    7141           2 : OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
    7142             : {
    7143             :     // Temporary remove foreign key checks
    7144             :     const GPKGTemporaryForeignKeyCheckDisabler
    7145           2 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7146             : 
    7147           2 :     OGRErr eErr = SoftStartTransaction();
    7148             : 
    7149           2 :     if (eErr == OGRERR_NONE)
    7150             :     {
    7151           2 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix WHERE "
    7152             :                                        "lower(table_name) = lower('%q')",
    7153             :                                        pszLayerName);
    7154           2 :         eErr = SQLCommand(hDB, pszSQL);
    7155           2 :         sqlite3_free(pszSQL);
    7156             :     }
    7157             : 
    7158           2 :     if (eErr == OGRERR_NONE)
    7159             :     {
    7160           2 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
    7161             :                                        "lower(table_name) = lower('%q')",
    7162             :                                        pszLayerName);
    7163           2 :         eErr = SQLCommand(hDB, pszSQL);
    7164           2 :         sqlite3_free(pszSQL);
    7165             :     }
    7166             : 
    7167           2 :     if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
    7168             :     {
    7169             :         char *pszSQL =
    7170           1 :             sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
    7171             :                             "WHERE lower(tile_matrix_set_name) = lower('%q')",
    7172             :                             pszLayerName);
    7173           1 :         eErr = SQLCommand(hDB, pszSQL);
    7174           1 :         sqlite3_free(pszSQL);
    7175             : 
    7176           1 :         if (eErr == OGRERR_NONE)
    7177             :         {
    7178             :             pszSQL =
    7179           1 :                 sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
    7180             :                                 "WHERE lower(tpudt_name) = lower('%q')",
    7181             :                                 pszLayerName);
    7182           1 :             eErr = SQLCommand(hDB, pszSQL);
    7183           1 :             sqlite3_free(pszSQL);
    7184             :         }
    7185             :     }
    7186             : 
    7187           2 :     if (eErr == OGRERR_NONE)
    7188             :     {
    7189           2 :         eErr = DeleteLayerCommon(pszLayerName);
    7190             :     }
    7191             : 
    7192           2 :     if (eErr == OGRERR_NONE)
    7193             :     {
    7194           2 :         eErr = SoftCommitTransaction();
    7195             :     }
    7196             :     else
    7197             :     {
    7198           0 :         SoftRollbackTransaction();
    7199             :     }
    7200             : 
    7201           4 :     return eErr;
    7202             : }
    7203             : 
    7204             : /************************************************************************/
    7205             : /*                    DeleteVectorOrRasterLayer()                       */
    7206             : /************************************************************************/
    7207             : 
    7208          13 : bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
    7209             : {
    7210             : 
    7211          13 :     int idx = FindLayerIndex(pszLayerName);
    7212          13 :     if (idx >= 0)
    7213             :     {
    7214           5 :         DeleteLayer(idx);
    7215           5 :         return true;
    7216             :     }
    7217             : 
    7218             :     char *pszSQL =
    7219           8 :         sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
    7220             :                         "lower(table_name) = lower('%q') "
    7221             :                         "AND data_type IN ('tiles', '2d-gridded-coverage')",
    7222             :                         pszLayerName);
    7223           8 :     bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
    7224           8 :     sqlite3_free(pszSQL);
    7225           8 :     if (bIsRasterTable)
    7226             :     {
    7227           2 :         DeleteRasterLayer(pszLayerName);
    7228           2 :         return true;
    7229             :     }
    7230           6 :     return false;
    7231             : }
    7232             : 
    7233           7 : bool GDALGeoPackageDataset::RenameVectorOrRasterLayer(
    7234             :     const char *pszLayerName, const char *pszNewLayerName)
    7235             : {
    7236           7 :     int idx = FindLayerIndex(pszLayerName);
    7237           7 :     if (idx >= 0)
    7238             :     {
    7239           4 :         m_papoLayers[idx]->Rename(pszNewLayerName);
    7240           4 :         return true;
    7241             :     }
    7242             : 
    7243             :     char *pszSQL =
    7244           3 :         sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
    7245             :                         "lower(table_name) = lower('%q') "
    7246             :                         "AND data_type IN ('tiles', '2d-gridded-coverage')",
    7247             :                         pszLayerName);
    7248           3 :     const bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
    7249           3 :     sqlite3_free(pszSQL);
    7250             : 
    7251           3 :     if (bIsRasterTable)
    7252             :     {
    7253           2 :         return RenameRasterLayer(pszLayerName, pszNewLayerName);
    7254             :     }
    7255             : 
    7256           1 :     return false;
    7257             : }
    7258             : 
    7259           2 : bool GDALGeoPackageDataset::RenameRasterLayer(const char *pszLayerName,
    7260             :                                               const char *pszNewLayerName)
    7261             : {
    7262           4 :     std::string osSQL;
    7263             : 
    7264           2 :     char *pszSQL = sqlite3_mprintf(
    7265             :         "SELECT 1 FROM sqlite_master WHERE lower(name) = lower('%q') "
    7266             :         "AND type IN ('table', 'view')",
    7267             :         pszNewLayerName);
    7268           2 :     const bool bAlreadyExists = SQLGetInteger(GetDB(), pszSQL, nullptr) == 1;
    7269           2 :     sqlite3_free(pszSQL);
    7270           2 :     if (bAlreadyExists)
    7271             :     {
    7272           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists",
    7273             :                  pszNewLayerName);
    7274           0 :         return false;
    7275             :     }
    7276             : 
    7277             :     // Temporary remove foreign key checks
    7278             :     const GPKGTemporaryForeignKeyCheckDisabler
    7279           4 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7280             : 
    7281           2 :     if (SoftStartTransaction() != OGRERR_NONE)
    7282             :     {
    7283           0 :         return false;
    7284             :     }
    7285             : 
    7286           2 :     pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET table_name = '%q' WHERE "
    7287             :                              "lower(table_name) = lower('%q');",
    7288             :                              pszNewLayerName, pszLayerName);
    7289           2 :     osSQL = pszSQL;
    7290           2 :     sqlite3_free(pszSQL);
    7291             : 
    7292           2 :     pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' WHERE "
    7293             :                              "lower(identifier) = lower('%q');",
    7294             :                              pszNewLayerName, pszLayerName);
    7295           2 :     osSQL += pszSQL;
    7296           2 :     sqlite3_free(pszSQL);
    7297             : 
    7298             :     pszSQL =
    7299           2 :         sqlite3_mprintf("UPDATE gpkg_tile_matrix SET table_name = '%q' WHERE "
    7300             :                         "lower(table_name) = lower('%q');",
    7301             :                         pszNewLayerName, pszLayerName);
    7302           2 :     osSQL += pszSQL;
    7303           2 :     sqlite3_free(pszSQL);
    7304             : 
    7305           2 :     pszSQL = sqlite3_mprintf(
    7306             :         "UPDATE gpkg_tile_matrix_set SET table_name = '%q' WHERE "
    7307             :         "lower(table_name) = lower('%q');",
    7308             :         pszNewLayerName, pszLayerName);
    7309           2 :     osSQL += pszSQL;
    7310           2 :     sqlite3_free(pszSQL);
    7311             : 
    7312           2 :     if (HasGriddedCoverageAncillaryTable())
    7313             :     {
    7314           1 :         pszSQL = sqlite3_mprintf("UPDATE gpkg_2d_gridded_coverage_ancillary "
    7315             :                                  "SET tile_matrix_set_name = '%q' WHERE "
    7316             :                                  "lower(tile_matrix_set_name) = lower('%q');",
    7317             :                                  pszNewLayerName, pszLayerName);
    7318           1 :         osSQL += pszSQL;
    7319           1 :         sqlite3_free(pszSQL);
    7320             : 
    7321           1 :         pszSQL = sqlite3_mprintf(
    7322             :             "UPDATE gpkg_2d_gridded_tile_ancillary SET tpudt_name = '%q' WHERE "
    7323             :             "lower(tpudt_name) = lower('%q');",
    7324             :             pszNewLayerName, pszLayerName);
    7325           1 :         osSQL += pszSQL;
    7326           1 :         sqlite3_free(pszSQL);
    7327             :     }
    7328             : 
    7329           2 :     if (HasExtensionsTable())
    7330             :     {
    7331           2 :         pszSQL = sqlite3_mprintf(
    7332             :             "UPDATE gpkg_extensions SET table_name = '%q' WHERE "
    7333             :             "lower(table_name) = lower('%q');",
    7334             :             pszNewLayerName, pszLayerName);
    7335           2 :         osSQL += pszSQL;
    7336           2 :         sqlite3_free(pszSQL);
    7337             :     }
    7338             : 
    7339           2 :     if (HasMetadataTables())
    7340             :     {
    7341           1 :         pszSQL = sqlite3_mprintf(
    7342             :             "UPDATE gpkg_metadata_reference SET table_name = '%q' WHERE "
    7343             :             "lower(table_name) = lower('%q');",
    7344             :             pszNewLayerName, pszLayerName);
    7345           1 :         osSQL += pszSQL;
    7346           1 :         sqlite3_free(pszSQL);
    7347             :     }
    7348             : 
    7349           2 :     if (HasDataColumnsTable())
    7350             :     {
    7351           0 :         pszSQL = sqlite3_mprintf(
    7352             :             "UPDATE gpkg_data_columns SET table_name = '%q' WHERE "
    7353             :             "lower(table_name) = lower('%q');",
    7354             :             pszNewLayerName, pszLayerName);
    7355           0 :         osSQL += pszSQL;
    7356           0 :         sqlite3_free(pszSQL);
    7357             :     }
    7358             : 
    7359           2 :     if (HasQGISLayerStyles())
    7360             :     {
    7361             :         // Update QGIS styles
    7362             :         pszSQL =
    7363           0 :             sqlite3_mprintf("UPDATE layer_styles SET f_table_name = '%q' WHERE "
    7364             :                             "lower(f_table_name) = lower('%q');",
    7365             :                             pszNewLayerName, pszLayerName);
    7366           0 :         osSQL += pszSQL;
    7367           0 :         sqlite3_free(pszSQL);
    7368             :     }
    7369             : 
    7370             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7371           2 :     if (m_bHasGPKGOGRContents)
    7372             :     {
    7373           2 :         pszSQL = sqlite3_mprintf(
    7374             :             "UPDATE gpkg_ogr_contents SET table_name = '%q' WHERE "
    7375             :             "lower(table_name) = lower('%q');",
    7376             :             pszNewLayerName, pszLayerName);
    7377           2 :         osSQL += pszSQL;
    7378           2 :         sqlite3_free(pszSQL);
    7379             :     }
    7380             : #endif
    7381             : 
    7382           2 :     if (HasGpkgextRelationsTable())
    7383             :     {
    7384           0 :         pszSQL = sqlite3_mprintf(
    7385             :             "UPDATE gpkgext_relations SET base_table_name = '%q' WHERE "
    7386             :             "lower(base_table_name) = lower('%q');",
    7387             :             pszNewLayerName, pszLayerName);
    7388           0 :         osSQL += pszSQL;
    7389           0 :         sqlite3_free(pszSQL);
    7390             : 
    7391           0 :         pszSQL = sqlite3_mprintf(
    7392             :             "UPDATE gpkgext_relations SET related_table_name = '%q' WHERE "
    7393             :             "lower(related_table_name) = lower('%q');",
    7394             :             pszNewLayerName, pszLayerName);
    7395           0 :         osSQL += pszSQL;
    7396           0 :         sqlite3_free(pszSQL);
    7397             : 
    7398           0 :         pszSQL = sqlite3_mprintf(
    7399             :             "UPDATE gpkgext_relations SET mapping_table_name = '%q' WHERE "
    7400             :             "lower(mapping_table_name) = lower('%q');",
    7401             :             pszNewLayerName, pszLayerName);
    7402           0 :         osSQL += pszSQL;
    7403           0 :         sqlite3_free(pszSQL);
    7404             :     }
    7405             : 
    7406             :     // Drop all triggers for the layer
    7407           2 :     pszSQL = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type = "
    7408             :                              "'trigger' AND tbl_name = '%q'",
    7409             :                              pszLayerName);
    7410           2 :     auto oTriggerResult = SQLQuery(GetDB(), pszSQL);
    7411           2 :     sqlite3_free(pszSQL);
    7412           2 :     if (oTriggerResult)
    7413             :     {
    7414          14 :         for (int i = 0; i < oTriggerResult->RowCount(); i++)
    7415             :         {
    7416          12 :             const char *pszTriggerName = oTriggerResult->GetValue(0, i);
    7417          12 :             pszSQL = sqlite3_mprintf("DROP TRIGGER IF EXISTS \"%w\";",
    7418             :                                      pszTriggerName);
    7419          12 :             osSQL += pszSQL;
    7420          12 :             sqlite3_free(pszSQL);
    7421             :         }
    7422             :     }
    7423             : 
    7424           2 :     pszSQL = sqlite3_mprintf("ALTER TABLE \"%w\" RENAME TO \"%w\";",
    7425             :                              pszLayerName, pszNewLayerName);
    7426           2 :     osSQL += pszSQL;
    7427           2 :     sqlite3_free(pszSQL);
    7428             : 
    7429             :     // Recreate all zoom/tile triggers
    7430           2 :     if (oTriggerResult)
    7431             :     {
    7432           2 :         osSQL += CreateRasterTriggersSQL(pszNewLayerName);
    7433             :     }
    7434             : 
    7435           2 :     OGRErr eErr = SQLCommand(GetDB(), osSQL.c_str());
    7436             : 
    7437             :     // Check foreign key integrity
    7438           2 :     if (eErr == OGRERR_NONE)
    7439             :     {
    7440           2 :         eErr = PragmaCheck("foreign_key_check", "", 0);
    7441             :     }
    7442             : 
    7443           2 :     if (eErr == OGRERR_NONE)
    7444             :     {
    7445           2 :         eErr = SoftCommitTransaction();
    7446             :     }
    7447             :     else
    7448             :     {
    7449           0 :         SoftRollbackTransaction();
    7450             :     }
    7451             : 
    7452           2 :     return eErr == OGRERR_NONE;
    7453             : }
    7454             : 
    7455             : /************************************************************************/
    7456             : /*                       TestCapability()                               */
    7457             : /************************************************************************/
    7458             : 
    7459         420 : int GDALGeoPackageDataset::TestCapability(const char *pszCap)
    7460             : {
    7461         420 :     if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
    7462         262 :         EQUAL(pszCap, "RenameLayer"))
    7463             :     {
    7464         158 :         return GetUpdate();
    7465             :     }
    7466         262 :     else if (EQUAL(pszCap, ODsCCurveGeometries))
    7467          12 :         return TRUE;
    7468         250 :     else if (EQUAL(pszCap, ODsCMeasuredGeometries))
    7469           8 :         return TRUE;
    7470         242 :     else if (EQUAL(pszCap, ODsCZGeometries))
    7471           8 :         return TRUE;
    7472         234 :     else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
    7473         234 :              EQUAL(pszCap, GDsCAddRelationship) ||
    7474         234 :              EQUAL(pszCap, GDsCDeleteRelationship) ||
    7475         234 :              EQUAL(pszCap, GDsCUpdateRelationship) ||
    7476         234 :              EQUAL(pszCap, ODsCAddFieldDomain))
    7477           1 :         return GetUpdate();
    7478             : 
    7479         233 :     return OGRSQLiteBaseDataSource::TestCapability(pszCap);
    7480             : }
    7481             : 
    7482             : /************************************************************************/
    7483             : /*                       ResetReadingAllLayers()                        */
    7484             : /************************************************************************/
    7485             : 
    7486         203 : void GDALGeoPackageDataset::ResetReadingAllLayers()
    7487             : {
    7488         411 :     for (int i = 0; i < m_nLayers; i++)
    7489             :     {
    7490         208 :         m_papoLayers[i]->ResetReading();
    7491             :     }
    7492         203 : }
    7493             : 
    7494             : /************************************************************************/
    7495             : /*                             ExecuteSQL()                             */
    7496             : /************************************************************************/
    7497             : 
    7498             : static const char *const apszFuncsWithSideEffects[] = {
    7499             :     "CreateSpatialIndex",
    7500             :     "DisableSpatialIndex",
    7501             :     "HasSpatialIndex",
    7502             :     "RegisterGeometryExtension",
    7503             : };
    7504             : 
    7505        5613 : OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
    7506             :                                             OGRGeometry *poSpatialFilter,
    7507             :                                             const char *pszDialect)
    7508             : 
    7509             : {
    7510        5613 :     m_bHasReadMetadataFromStorage = false;
    7511             : 
    7512        5613 :     FlushMetadata();
    7513             : 
    7514        5631 :     while (*pszSQLCommand != '\0' &&
    7515        5631 :            isspace(static_cast<unsigned char>(*pszSQLCommand)))
    7516          18 :         pszSQLCommand++;
    7517             : 
    7518       11226 :     CPLString osSQLCommand(pszSQLCommand);
    7519        5613 :     if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
    7520          48 :         osSQLCommand.pop_back();
    7521             : 
    7522        5613 :     if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
    7523             :     {
    7524             :         // Some SQL commands will influence the feature count behind our
    7525             :         // back, so disable it in that case.
    7526             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7527             :         const bool bInsertOrDelete =
    7528        5544 :             osSQLCommand.ifind("insert into ") != std::string::npos ||
    7529        2432 :             osSQLCommand.ifind("insert or replace into ") !=
    7530        7976 :                 std::string::npos ||
    7531        2386 :             osSQLCommand.ifind("delete from ") != std::string::npos;
    7532             :         const bool bRollback =
    7533        5544 :             osSQLCommand.ifind("rollback ") != std::string::npos;
    7534             : #endif
    7535             : 
    7536        7338 :         for (int i = 0; i < m_nLayers; i++)
    7537             :         {
    7538        1794 :             if (m_papoLayers[i]->SyncToDisk() != OGRERR_NONE)
    7539           0 :                 return nullptr;
    7540             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7541        1995 :             if (bRollback || (bInsertOrDelete &&
    7542         201 :                               osSQLCommand.ifind(m_papoLayers[i]->GetName()) !=
    7543             :                                   std::string::npos))
    7544             :             {
    7545         199 :                 m_papoLayers[i]->DisableFeatureCount();
    7546             :             }
    7547             : #endif
    7548             :         }
    7549             :     }
    7550             : 
    7551        5613 :     if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
    7552        5612 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
    7553        5612 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
    7554        5612 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
    7555             :     {
    7556           1 :         OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
    7557             :     }
    7558        5612 :     else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
    7559        5611 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
    7560        5611 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
    7561        5611 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
    7562             :     {
    7563           1 :         OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
    7564             :     }
    7565             : 
    7566             :     /* -------------------------------------------------------------------- */
    7567             :     /*      DEBUG "SELECT nolock" command.                                  */
    7568             :     /* -------------------------------------------------------------------- */
    7569        5682 :     if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
    7570          69 :         EQUAL(osSQLCommand, "SELECT nolock"))
    7571             :     {
    7572           3 :         return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
    7573             :     }
    7574             : 
    7575             :     /* -------------------------------------------------------------------- */
    7576             :     /*      Special case DELLAYER: command.                                 */
    7577             :     /* -------------------------------------------------------------------- */
    7578        5610 :     if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
    7579             :     {
    7580           4 :         const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
    7581             : 
    7582           4 :         while (*pszLayerName == ' ')
    7583           0 :             pszLayerName++;
    7584             : 
    7585           4 :         if (!DeleteVectorOrRasterLayer(pszLayerName))
    7586             :         {
    7587           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
    7588             :                      pszLayerName);
    7589             :         }
    7590           4 :         return nullptr;
    7591             :     }
    7592             : 
    7593             :     /* -------------------------------------------------------------------- */
    7594             :     /*      Special case RECOMPUTE EXTENT ON command.                       */
    7595             :     /* -------------------------------------------------------------------- */
    7596        5606 :     if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
    7597             :     {
    7598             :         const char *pszLayerName =
    7599           4 :             osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
    7600             : 
    7601           4 :         while (*pszLayerName == ' ')
    7602           0 :             pszLayerName++;
    7603             : 
    7604           4 :         int idx = FindLayerIndex(pszLayerName);
    7605           4 :         if (idx >= 0)
    7606             :         {
    7607           4 :             m_papoLayers[idx]->RecomputeExtent();
    7608             :         }
    7609             :         else
    7610           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
    7611             :                      pszLayerName);
    7612           4 :         return nullptr;
    7613             :     }
    7614             : 
    7615             :     /* -------------------------------------------------------------------- */
    7616             :     /*      Intercept DROP TABLE                                            */
    7617             :     /* -------------------------------------------------------------------- */
    7618        5602 :     if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
    7619             :     {
    7620           9 :         const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
    7621             : 
    7622           9 :         while (*pszLayerName == ' ')
    7623           0 :             pszLayerName++;
    7624             : 
    7625           9 :         if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
    7626           4 :             return nullptr;
    7627             :     }
    7628             : 
    7629             :     /* -------------------------------------------------------------------- */
    7630             :     /*      Intercept ALTER TABLE src_table RENAME TO dst_table             */
    7631             :     /*      and       ALTER TABLE table RENAME COLUMN src_name TO dst_name  */
    7632             :     /*      and       ALTER TABLE table DROP COLUMN col_name                */
    7633             :     /*                                                                      */
    7634             :     /*      We do this because SQLite mechanisms can't deal with updating   */
    7635             :     /*      literal values in gpkg_ tables that refer to table and column   */
    7636             :     /*      names.                                                          */
    7637             :     /* -------------------------------------------------------------------- */
    7638        5598 :     if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
    7639             :     {
    7640           9 :         char **papszTokens = SQLTokenize(osSQLCommand);
    7641             :         /* ALTER TABLE src_table RENAME TO dst_table */
    7642          16 :         if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
    7643           7 :             EQUAL(papszTokens[4], "TO"))
    7644             :         {
    7645           7 :             const char *pszSrcTableName = papszTokens[2];
    7646           7 :             const char *pszDstTableName = papszTokens[5];
    7647           7 :             if (RenameVectorOrRasterLayer(SQLUnescape(pszSrcTableName),
    7648          14 :                                           SQLUnescape(pszDstTableName)))
    7649             :             {
    7650           6 :                 CSLDestroy(papszTokens);
    7651           6 :                 return nullptr;
    7652             :             }
    7653             :         }
    7654             :         /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
    7655           2 :         else if (CSLCount(papszTokens) == 8 &&
    7656           1 :                  EQUAL(papszTokens[3], "RENAME") &&
    7657           3 :                  EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
    7658             :         {
    7659           1 :             const char *pszTableName = papszTokens[2];
    7660           1 :             const char *pszSrcColumn = papszTokens[5];
    7661           1 :             const char *pszDstColumn = papszTokens[7];
    7662             :             OGRGeoPackageTableLayer *poLayer =
    7663           0 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7664           1 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7665           1 :             if (poLayer)
    7666             :             {
    7667           2 :                 int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
    7668           2 :                     SQLUnescape(pszSrcColumn));
    7669           1 :                 if (nSrcFieldIdx >= 0)
    7670             :                 {
    7671             :                     // OFTString or any type will do as we just alter the name
    7672             :                     // so it will be ignored.
    7673           1 :                     OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
    7674           1 :                                             OFTString);
    7675           1 :                     poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
    7676             :                                             ALTER_NAME_FLAG);
    7677           1 :                     CSLDestroy(papszTokens);
    7678           1 :                     return nullptr;
    7679             :                 }
    7680             :             }
    7681             :         }
    7682             :         /* ALTER TABLE table DROP COLUMN col_name */
    7683           2 :         else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
    7684           1 :                  EQUAL(papszTokens[4], "COLUMN"))
    7685             :         {
    7686           1 :             const char *pszTableName = papszTokens[2];
    7687           1 :             const char *pszColumnName = papszTokens[5];
    7688             :             OGRGeoPackageTableLayer *poLayer =
    7689           0 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7690           1 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7691           1 :             if (poLayer)
    7692             :             {
    7693           2 :                 int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
    7694           2 :                     SQLUnescape(pszColumnName));
    7695           1 :                 if (nFieldIdx >= 0)
    7696             :                 {
    7697           1 :                     poLayer->DeleteField(nFieldIdx);
    7698           1 :                     CSLDestroy(papszTokens);
    7699           1 :                     return nullptr;
    7700             :                 }
    7701             :             }
    7702             :         }
    7703           1 :         CSLDestroy(papszTokens);
    7704             :     }
    7705             : 
    7706        5590 :     if (ProcessTransactionSQL(osSQLCommand))
    7707             :     {
    7708         253 :         return nullptr;
    7709             :     }
    7710             : 
    7711        5337 :     if (EQUAL(osSQLCommand, "VACUUM"))
    7712             :     {
    7713          12 :         ResetReadingAllLayers();
    7714             :     }
    7715        5325 :     else if (STARTS_WITH_CI(osSQLCommand, "DELETE FROM "))
    7716             :     {
    7717             :         // Optimize truncation of a table, especially if it has a spatial
    7718             :         // index.
    7719          20 :         const CPLStringList aosTokens(SQLTokenize(osSQLCommand));
    7720          20 :         if (aosTokens.size() == 3)
    7721             :         {
    7722          14 :             const char *pszTableName = aosTokens[2];
    7723             :             OGRGeoPackageTableLayer *poLayer =
    7724           8 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7725          22 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7726          14 :             if (poLayer)
    7727             :             {
    7728           6 :                 poLayer->Truncate();
    7729           6 :                 return nullptr;
    7730             :             }
    7731             :         }
    7732             :     }
    7733        5305 :     else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
    7734           1 :         return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
    7735        5304 :     else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
    7736          66 :              !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
    7737          66 :              !EQUAL(pszDialect, "DEBUG"))
    7738           0 :         return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
    7739           0 :                                        pszDialect);
    7740             : 
    7741             :     /* -------------------------------------------------------------------- */
    7742             :     /*      Prepare statement.                                              */
    7743             :     /* -------------------------------------------------------------------- */
    7744        5330 :     sqlite3_stmt *hSQLStmt = nullptr;
    7745             : 
    7746             :     /* This will speed-up layer creation */
    7747             :     /* ORDER BY are costly to evaluate and are not necessary to establish */
    7748             :     /* the layer definition. */
    7749        5330 :     bool bUseStatementForGetNextFeature = true;
    7750        5330 :     bool bEmptyLayer = false;
    7751       10660 :     CPLString osSQLCommandTruncated(osSQLCommand);
    7752             : 
    7753       17578 :     if (osSQLCommand.ifind("SELECT ") == 0 &&
    7754        6124 :         CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
    7755         760 :             std::string::npos &&
    7756         760 :         osSQLCommand.ifind(" UNION ") == std::string::npos &&
    7757        6884 :         osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
    7758         760 :         osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
    7759             :     {
    7760         760 :         size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
    7761         760 :         if (nOrderByPos != std::string::npos)
    7762             :         {
    7763           8 :             osSQLCommandTruncated.resize(nOrderByPos);
    7764           8 :             bUseStatementForGetNextFeature = false;
    7765             :         }
    7766             :     }
    7767             : 
    7768        5330 :     int rc = prepareSql(hDB, osSQLCommandTruncated.c_str(),
    7769        5330 :                         static_cast<int>(osSQLCommandTruncated.size()),
    7770             :                         &hSQLStmt, nullptr);
    7771             : 
    7772        5330 :     if (rc != SQLITE_OK)
    7773             :     {
    7774           9 :         CPLError(CE_Failure, CPLE_AppDefined,
    7775             :                  "In ExecuteSQL(): sqlite3_prepare_v2(%s):\n  %s",
    7776             :                  osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
    7777             : 
    7778           9 :         if (hSQLStmt != nullptr)
    7779             :         {
    7780           0 :             sqlite3_finalize(hSQLStmt);
    7781             :         }
    7782             : 
    7783           9 :         return nullptr;
    7784             :     }
    7785             : 
    7786             :     /* -------------------------------------------------------------------- */
    7787             :     /*      Do we get a resultset?                                          */
    7788             :     /* -------------------------------------------------------------------- */
    7789        5321 :     rc = sqlite3_step(hSQLStmt);
    7790             : 
    7791        6884 :     for (int i = 0; i < m_nLayers; i++)
    7792             :     {
    7793        1563 :         m_papoLayers[i]->RunDeferredDropRTreeTableIfNecessary();
    7794             :     }
    7795             : 
    7796        5321 :     if (rc != SQLITE_ROW)
    7797             :     {
    7798        4607 :         if (rc != SQLITE_DONE)
    7799             :         {
    7800           7 :             CPLError(CE_Failure, CPLE_AppDefined,
    7801             :                      "In ExecuteSQL(): sqlite3_step(%s):\n  %s",
    7802             :                      osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
    7803             : 
    7804           7 :             sqlite3_finalize(hSQLStmt);
    7805           7 :             return nullptr;
    7806             :         }
    7807             : 
    7808        4600 :         if (EQUAL(osSQLCommand, "VACUUM"))
    7809             :         {
    7810          12 :             sqlite3_finalize(hSQLStmt);
    7811             :             /* VACUUM rewrites the DB, so we need to reset the application id */
    7812          12 :             SetApplicationAndUserVersionId();
    7813          12 :             return nullptr;
    7814             :         }
    7815             : 
    7816        4588 :         if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
    7817             :         {
    7818        4464 :             sqlite3_finalize(hSQLStmt);
    7819        4464 :             return nullptr;
    7820             :         }
    7821             : 
    7822         124 :         bUseStatementForGetNextFeature = false;
    7823         124 :         bEmptyLayer = true;
    7824             :     }
    7825             : 
    7826             :     /* -------------------------------------------------------------------- */
    7827             :     /*      Special case for some functions which must be run               */
    7828             :     /*      only once                                                       */
    7829             :     /* -------------------------------------------------------------------- */
    7830         838 :     if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
    7831             :     {
    7832        3814 :         for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
    7833             :                                          sizeof(apszFuncsWithSideEffects[0]);
    7834             :              i++)
    7835             :         {
    7836        3077 :             if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
    7837             :                        strlen(apszFuncsWithSideEffects[i])))
    7838             :             {
    7839         112 :                 if (sqlite3_column_count(hSQLStmt) == 1 &&
    7840          56 :                     sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
    7841             :                 {
    7842          56 :                     int ret = sqlite3_column_int(hSQLStmt, 0);
    7843             : 
    7844          56 :                     sqlite3_finalize(hSQLStmt);
    7845             : 
    7846             :                     return new OGRSQLiteSingleFeatureLayer(
    7847          56 :                         apszFuncsWithSideEffects[i], ret);
    7848             :                 }
    7849             :             }
    7850             :         }
    7851             :     }
    7852          45 :     else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
    7853             :     {
    7854          63 :         if (sqlite3_column_count(hSQLStmt) == 1 &&
    7855          18 :             sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
    7856             :         {
    7857          15 :             int ret = sqlite3_column_int(hSQLStmt, 0);
    7858             : 
    7859          15 :             sqlite3_finalize(hSQLStmt);
    7860             : 
    7861          15 :             return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
    7862          15 :                                                    ret);
    7863             :         }
    7864          33 :         else if (sqlite3_column_count(hSQLStmt) == 1 &&
    7865           3 :                  sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
    7866             :         {
    7867             :             const char *pszRet = reinterpret_cast<const char *>(
    7868           3 :                 sqlite3_column_text(hSQLStmt, 0));
    7869             : 
    7870             :             OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
    7871           3 :                 osSQLCommand.c_str() + 7, pszRet);
    7872             : 
    7873           3 :             sqlite3_finalize(hSQLStmt);
    7874             : 
    7875           3 :             return poRet;
    7876             :         }
    7877             :     }
    7878             : 
    7879             :     /* -------------------------------------------------------------------- */
    7880             :     /*      Create layer.                                                   */
    7881             :     /* -------------------------------------------------------------------- */
    7882             : 
    7883             :     OGRLayer *poLayer = new OGRGeoPackageSelectLayer(
    7884             :         this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
    7885         764 :         bEmptyLayer);
    7886             : 
    7887         767 :     if (poSpatialFilter != nullptr &&
    7888           3 :         poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
    7889           3 :         poLayer->SetSpatialFilter(0, poSpatialFilter);
    7890             : 
    7891         764 :     return poLayer;
    7892             : }
    7893             : 
    7894             : /************************************************************************/
    7895             : /*                          ReleaseResultSet()                          */
    7896             : /************************************************************************/
    7897             : 
    7898         794 : void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
    7899             : 
    7900             : {
    7901         794 :     delete poLayer;
    7902         794 : }
    7903             : 
    7904             : /************************************************************************/
    7905             : /*                         HasExtensionsTable()                         */
    7906             : /************************************************************************/
    7907             : 
    7908        6297 : bool GDALGeoPackageDataset::HasExtensionsTable()
    7909             : {
    7910        6297 :     return SQLGetInteger(
    7911             :                hDB,
    7912             :                "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
    7913             :                "AND type IN ('table', 'view')",
    7914        6297 :                nullptr) == 1;
    7915             : }
    7916             : 
    7917             : /************************************************************************/
    7918             : /*                    CheckUnknownExtensions()                          */
    7919             : /************************************************************************/
    7920             : 
    7921        1430 : void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
    7922             : {
    7923        1430 :     if (!HasExtensionsTable())
    7924         193 :         return;
    7925             : 
    7926        1237 :     char *pszSQL = nullptr;
    7927        1237 :     if (!bCheckRasterTable)
    7928        1030 :         pszSQL = sqlite3_mprintf(
    7929             :             "SELECT extension_name, definition, scope FROM gpkg_extensions "
    7930             :             "WHERE (table_name IS NULL "
    7931             :             "AND extension_name IS NOT NULL "
    7932             :             "AND definition IS NOT NULL "
    7933             :             "AND scope IS NOT NULL "
    7934             :             "AND extension_name NOT IN ("
    7935             :             "'gdal_aspatial', "
    7936             :             "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
    7937             :             "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
    7938             :                                        // 17-066r1 finalization
    7939             :             "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
    7940             :             "'gpkg_metadata', "
    7941             :             "'gpkg_schema', "
    7942             :             "'gpkg_crs_wkt', "
    7943             :             "'gpkg_crs_wkt_1_1', "
    7944             :             "'related_tables', 'gpkg_related_tables')) "
    7945             : #ifdef WORKAROUND_SQLITE3_BUGS
    7946             :             "OR 0 "
    7947             : #endif
    7948             :             "LIMIT 1000");
    7949             :     else
    7950         207 :         pszSQL = sqlite3_mprintf(
    7951             :             "SELECT extension_name, definition, scope FROM gpkg_extensions "
    7952             :             "WHERE (lower(table_name) = lower('%q') "
    7953             :             "AND extension_name IS NOT NULL "
    7954             :             "AND definition IS NOT NULL "
    7955             :             "AND scope IS NOT NULL "
    7956             :             "AND extension_name NOT IN ("
    7957             :             "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
    7958             :             "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
    7959             :                                        // 17-066r1 finalization
    7960             :             "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
    7961             :             "'gpkg_metadata', "
    7962             :             "'gpkg_schema', "
    7963             :             "'gpkg_crs_wkt', "
    7964             :             "'gpkg_crs_wkt_1_1', "
    7965             :             "'related_tables', 'gpkg_related_tables')) "
    7966             : #ifdef WORKAROUND_SQLITE3_BUGS
    7967             :             "OR 0 "
    7968             : #endif
    7969             :             "LIMIT 1000",
    7970             :             m_osRasterTable.c_str());
    7971             : 
    7972        2474 :     auto oResultTable = SQLQuery(GetDB(), pszSQL);
    7973        1237 :     sqlite3_free(pszSQL);
    7974        1237 :     if (oResultTable && oResultTable->RowCount() > 0)
    7975             :     {
    7976          44 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    7977             :         {
    7978          22 :             const char *pszExtName = oResultTable->GetValue(0, i);
    7979          22 :             const char *pszDefinition = oResultTable->GetValue(1, i);
    7980          22 :             const char *pszScope = oResultTable->GetValue(2, i);
    7981          22 :             if (pszExtName == nullptr || pszDefinition == nullptr ||
    7982             :                 pszScope == nullptr)
    7983             :             {
    7984           0 :                 continue;
    7985             :             }
    7986             : 
    7987          22 :             if (EQUAL(pszExtName, "gpkg_webp"))
    7988             :             {
    7989          16 :                 if (GDALGetDriverByName("WEBP") == nullptr)
    7990             :                 {
    7991           1 :                     CPLError(
    7992             :                         CE_Warning, CPLE_AppDefined,
    7993             :                         "Table %s contains WEBP tiles, but GDAL configured "
    7994             :                         "without WEBP support. Data will be missing",
    7995             :                         m_osRasterTable.c_str());
    7996             :                 }
    7997          16 :                 m_eTF = GPKG_TF_WEBP;
    7998          16 :                 continue;
    7999             :             }
    8000           6 :             if (EQUAL(pszExtName, "gpkg_zoom_other"))
    8001             :             {
    8002           2 :                 m_bZoomOther = true;
    8003           2 :                 continue;
    8004             :             }
    8005             : 
    8006           4 :             if (GetUpdate() && EQUAL(pszScope, "write-only"))
    8007             :             {
    8008           1 :                 CPLError(
    8009             :                     CE_Warning, CPLE_AppDefined,
    8010             :                     "Database relies on the '%s' (%s) extension that should "
    8011             :                     "be implemented for safe write-support, but is not "
    8012             :                     "currently. "
    8013             :                     "Update of that database are strongly discouraged to avoid "
    8014             :                     "corruption.",
    8015             :                     pszExtName, pszDefinition);
    8016             :             }
    8017           3 :             else if (GetUpdate() && EQUAL(pszScope, "read-write"))
    8018             :             {
    8019           1 :                 CPLError(
    8020             :                     CE_Warning, CPLE_AppDefined,
    8021             :                     "Database relies on the '%s' (%s) extension that should "
    8022             :                     "be implemented in order to read/write it safely, but is "
    8023             :                     "not currently. "
    8024             :                     "Some data may be missing while reading that database, and "
    8025             :                     "updates are strongly discouraged.",
    8026             :                     pszExtName, pszDefinition);
    8027             :             }
    8028           2 :             else if (EQUAL(pszScope, "read-write") &&
    8029             :                      // None of the NGA extensions at
    8030             :                      // http://ngageoint.github.io/GeoPackage/docs/extensions/
    8031             :                      // affect read-only scenarios
    8032           1 :                      !STARTS_WITH(pszExtName, "nga_"))
    8033             :             {
    8034           1 :                 CPLError(
    8035             :                     CE_Warning, CPLE_AppDefined,
    8036             :                     "Database relies on the '%s' (%s) extension that should "
    8037             :                     "be implemented in order to read it safely, but is not "
    8038             :                     "currently. "
    8039             :                     "Some data may be missing while reading that database.",
    8040             :                     pszExtName, pszDefinition);
    8041             :             }
    8042             :         }
    8043             :     }
    8044             : }
    8045             : 
    8046             : /************************************************************************/
    8047             : /*                         HasGDALAspatialExtension()                       */
    8048             : /************************************************************************/
    8049             : 
    8050         977 : bool GDALGeoPackageDataset::HasGDALAspatialExtension()
    8051             : {
    8052         977 :     if (!HasExtensionsTable())
    8053          86 :         return false;
    8054             : 
    8055             :     auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
    8056             :                                       "WHERE (extension_name = 'gdal_aspatial' "
    8057             :                                       "AND table_name IS NULL "
    8058             :                                       "AND column_name IS NULL)"
    8059             : #ifdef WORKAROUND_SQLITE3_BUGS
    8060             :                                       " OR 0"
    8061             : #endif
    8062         891 :     );
    8063         891 :     bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
    8064         891 :     return bHasExtension;
    8065             : }
    8066             : 
    8067             : std::string
    8068         186 : GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName)
    8069             : {
    8070             :     char *pszSQL;
    8071         186 :     std::string osSQL;
    8072             :     /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
    8073             :      * Definition SQL  */
    8074         186 :     pszSQL = sqlite3_mprintf(
    8075             :         "CREATE TRIGGER \"%w_zoom_insert\" "
    8076             :         "BEFORE INSERT ON \"%w\" "
    8077             :         "FOR EACH ROW BEGIN "
    8078             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8079             :         "constraint: zoom_level not specified for table in "
    8080             :         "gpkg_tile_matrix') "
    8081             :         "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
    8082             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
    8083             :         "END; "
    8084             :         "CREATE TRIGGER \"%w_zoom_update\" "
    8085             :         "BEFORE UPDATE OF zoom_level ON \"%w\" "
    8086             :         "FOR EACH ROW BEGIN "
    8087             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8088             :         "constraint: zoom_level not specified for table in "
    8089             :         "gpkg_tile_matrix') "
    8090             :         "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
    8091             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
    8092             :         "END; "
    8093             :         "CREATE TRIGGER \"%w_tile_column_insert\" "
    8094             :         "BEFORE INSERT ON \"%w\" "
    8095             :         "FOR EACH ROW BEGIN "
    8096             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8097             :         "constraint: tile_column cannot be < 0') "
    8098             :         "WHERE (NEW.tile_column < 0) ; "
    8099             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8100             :         "constraint: tile_column must by < matrix_width specified for "
    8101             :         "table and zoom level in gpkg_tile_matrix') "
    8102             :         "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
    8103             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8104             :         "zoom_level = NEW.zoom_level)); "
    8105             :         "END; "
    8106             :         "CREATE TRIGGER \"%w_tile_column_update\" "
    8107             :         "BEFORE UPDATE OF tile_column ON \"%w\" "
    8108             :         "FOR EACH ROW BEGIN "
    8109             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8110             :         "constraint: tile_column cannot be < 0') "
    8111             :         "WHERE (NEW.tile_column < 0) ; "
    8112             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8113             :         "constraint: tile_column must by < matrix_width specified for "
    8114             :         "table and zoom level in gpkg_tile_matrix') "
    8115             :         "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
    8116             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8117             :         "zoom_level = NEW.zoom_level)); "
    8118             :         "END; "
    8119             :         "CREATE TRIGGER \"%w_tile_row_insert\" "
    8120             :         "BEFORE INSERT ON \"%w\" "
    8121             :         "FOR EACH ROW BEGIN "
    8122             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8123             :         "constraint: tile_row cannot be < 0') "
    8124             :         "WHERE (NEW.tile_row < 0) ; "
    8125             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8126             :         "constraint: tile_row must by < matrix_height specified for "
    8127             :         "table and zoom level in gpkg_tile_matrix') "
    8128             :         "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
    8129             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8130             :         "zoom_level = NEW.zoom_level)); "
    8131             :         "END; "
    8132             :         "CREATE TRIGGER \"%w_tile_row_update\" "
    8133             :         "BEFORE UPDATE OF tile_row ON \"%w\" "
    8134             :         "FOR EACH ROW BEGIN "
    8135             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8136             :         "constraint: tile_row cannot be < 0') "
    8137             :         "WHERE (NEW.tile_row < 0) ; "
    8138             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8139             :         "constraint: tile_row must by < matrix_height specified for "
    8140             :         "table and zoom level in gpkg_tile_matrix') "
    8141             :         "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
    8142             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8143             :         "zoom_level = NEW.zoom_level)); "
    8144             :         "END; ",
    8145             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8146             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8147             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8148             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8149             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8150             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8151             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8152             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8153             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8154             :         osTableName.c_str());
    8155         186 :     osSQL = pszSQL;
    8156         186 :     sqlite3_free(pszSQL);
    8157         186 :     return osSQL;
    8158             : }
    8159             : 
    8160             : /************************************************************************/
    8161             : /*                  CreateExtensionsTableIfNecessary()                  */
    8162             : /************************************************************************/
    8163             : 
    8164        1118 : OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
    8165             : {
    8166             :     /* Check if the table gpkg_extensions exists */
    8167        1118 :     if (HasExtensionsTable())
    8168         396 :         return OGRERR_NONE;
    8169             : 
    8170             :     /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
    8171             :     /* in a corresponding row in the gpkg_extensions table. The absence of a */
    8172             :     /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
    8173             :     /* SHALL both indicate the absence of extensions to a GeoPackage. */
    8174         722 :     const char *pszCreateGpkgExtensions =
    8175             :         "CREATE TABLE gpkg_extensions ("
    8176             :         "table_name TEXT,"
    8177             :         "column_name TEXT,"
    8178             :         "extension_name TEXT NOT NULL,"
    8179             :         "definition TEXT NOT NULL,"
    8180             :         "scope TEXT NOT NULL,"
    8181             :         "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
    8182             :         ")";
    8183             : 
    8184         722 :     return SQLCommand(hDB, pszCreateGpkgExtensions);
    8185             : }
    8186             : 
    8187             : /************************************************************************/
    8188             : /*                    OGR_GPKG_Intersects_Spatial_Filter()              */
    8189             : /************************************************************************/
    8190             : 
    8191       23135 : void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
    8192             :                                         sqlite3_value **argv)
    8193             : {
    8194       23135 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8195             :     {
    8196           0 :         sqlite3_result_int(pContext, 0);
    8197       23125 :         return;
    8198             :     }
    8199             : 
    8200             :     auto poLayer =
    8201       23135 :         static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
    8202             : 
    8203       23135 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8204             :     const GByte *pabyBLOB =
    8205       23135 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8206             : 
    8207             :     GPkgHeader sHeader;
    8208       46270 :     if (poLayer->m_bFilterIsEnvelope &&
    8209       23135 :         OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
    8210             :     {
    8211       23135 :         if (sHeader.bExtentHasXY)
    8212             :         {
    8213          95 :             OGREnvelope sEnvelope;
    8214          95 :             sEnvelope.MinX = sHeader.MinX;
    8215          95 :             sEnvelope.MinY = sHeader.MinY;
    8216          95 :             sEnvelope.MaxX = sHeader.MaxX;
    8217          95 :             sEnvelope.MaxY = sHeader.MaxY;
    8218          95 :             if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
    8219             :             {
    8220          31 :                 sqlite3_result_int(pContext, 1);
    8221          31 :                 return;
    8222             :             }
    8223             :         }
    8224             : 
    8225             :         // Check if at least one point falls into the layer filter envelope
    8226             :         // nHeaderLen is > 0 for GeoPackage geometries
    8227       46208 :         if (sHeader.nHeaderLen > 0 &&
    8228       23104 :             OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
    8229       23104 :                                         nBLOBLen - sHeader.nHeaderLen,
    8230       23104 :                                         poLayer->m_sFilterEnvelope))
    8231             :         {
    8232       23094 :             sqlite3_result_int(pContext, 1);
    8233       23094 :             return;
    8234             :         }
    8235             :     }
    8236             : 
    8237             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8238          10 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8239          10 :     if (poGeom == nullptr)
    8240             :     {
    8241             :         // Try also spatialite geometry blobs
    8242           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8243           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8244           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8245             :         {
    8246           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8247           0 :             sqlite3_result_int(pContext, 0);
    8248           0 :             return;
    8249             :         }
    8250           0 :         poGeom.reset(poGeomSpatialite);
    8251             :     }
    8252             : 
    8253          10 :     sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
    8254             : }
    8255             : 
    8256             : /************************************************************************/
    8257             : /*                      OGRGeoPackageSTMinX()                           */
    8258             : /************************************************************************/
    8259             : 
    8260      242667 : static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
    8261             :                                 sqlite3_value **argv)
    8262             : {
    8263             :     GPkgHeader sHeader;
    8264      242667 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8265             :     {
    8266           3 :         sqlite3_result_null(pContext);
    8267           3 :         return;
    8268             :     }
    8269      242664 :     sqlite3_result_double(pContext, sHeader.MinX);
    8270             : }
    8271             : 
    8272             : /************************************************************************/
    8273             : /*                      OGRGeoPackageSTMinY()                           */
    8274             : /************************************************************************/
    8275             : 
    8276      242665 : static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
    8277             :                                 sqlite3_value **argv)
    8278             : {
    8279             :     GPkgHeader sHeader;
    8280      242665 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8281             :     {
    8282           1 :         sqlite3_result_null(pContext);
    8283           1 :         return;
    8284             :     }
    8285      242664 :     sqlite3_result_double(pContext, sHeader.MinY);
    8286             : }
    8287             : 
    8288             : /************************************************************************/
    8289             : /*                      OGRGeoPackageSTMaxX()                           */
    8290             : /************************************************************************/
    8291             : 
    8292      242665 : static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
    8293             :                                 sqlite3_value **argv)
    8294             : {
    8295             :     GPkgHeader sHeader;
    8296      242665 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8297             :     {
    8298           1 :         sqlite3_result_null(pContext);
    8299           1 :         return;
    8300             :     }
    8301      242664 :     sqlite3_result_double(pContext, sHeader.MaxX);
    8302             : }
    8303             : 
    8304             : /************************************************************************/
    8305             : /*                      OGRGeoPackageSTMaxY()                           */
    8306             : /************************************************************************/
    8307             : 
    8308      242665 : static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
    8309             :                                 sqlite3_value **argv)
    8310             : {
    8311             :     GPkgHeader sHeader;
    8312      242665 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8313             :     {
    8314           1 :         sqlite3_result_null(pContext);
    8315           1 :         return;
    8316             :     }
    8317      242664 :     sqlite3_result_double(pContext, sHeader.MaxY);
    8318             : }
    8319             : 
    8320             : /************************************************************************/
    8321             : /*                     OGRGeoPackageSTIsEmpty()                         */
    8322             : /************************************************************************/
    8323             : 
    8324      243999 : static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
    8325             :                                    sqlite3_value **argv)
    8326             : {
    8327             :     GPkgHeader sHeader;
    8328      243999 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8329             :     {
    8330           2 :         sqlite3_result_null(pContext);
    8331           2 :         return;
    8332             :     }
    8333      243997 :     sqlite3_result_int(pContext, sHeader.bEmpty);
    8334             : }
    8335             : 
    8336             : /************************************************************************/
    8337             : /*                    OGRGeoPackageSTGeometryType()                     */
    8338             : /************************************************************************/
    8339             : 
    8340           7 : static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
    8341             :                                         sqlite3_value **argv)
    8342             : {
    8343             :     GPkgHeader sHeader;
    8344             : 
    8345           7 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8346             :     const GByte *pabyBLOB =
    8347           7 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8348             :     OGRwkbGeometryType eGeometryType;
    8349             : 
    8350          13 :     if (nBLOBLen < 8 ||
    8351           6 :         GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
    8352             :     {
    8353           2 :         if (OGRSQLiteGetSpatialiteGeometryHeader(
    8354             :                 pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
    8355           2 :                 nullptr, nullptr, nullptr) == OGRERR_NONE)
    8356             :         {
    8357           1 :             sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
    8358             :                                 SQLITE_TRANSIENT);
    8359           4 :             return;
    8360             :         }
    8361             :         else
    8362             :         {
    8363           1 :             sqlite3_result_null(pContext);
    8364           1 :             return;
    8365             :         }
    8366             :     }
    8367             : 
    8368           5 :     if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
    8369             :     {
    8370           2 :         sqlite3_result_null(pContext);
    8371           2 :         return;
    8372             :     }
    8373             : 
    8374           3 :     OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
    8375             :                                         wkbVariantIso, &eGeometryType);
    8376           3 :     if (err != OGRERR_NONE)
    8377           1 :         sqlite3_result_null(pContext);
    8378             :     else
    8379           2 :         sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
    8380             :                             SQLITE_TRANSIENT);
    8381             : }
    8382             : 
    8383             : /************************************************************************/
    8384             : /*                 OGRGeoPackageSTEnvelopesIntersects()                 */
    8385             : /************************************************************************/
    8386             : 
    8387         118 : static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
    8388             :                                                int argc, sqlite3_value **argv)
    8389             : {
    8390             :     GPkgHeader sHeader;
    8391         118 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8392             :     {
    8393           2 :         sqlite3_result_int(pContext, FALSE);
    8394         107 :         return;
    8395             :     }
    8396         116 :     const double dfMinX = sqlite3_value_double(argv[1]);
    8397         116 :     if (sHeader.MaxX < dfMinX)
    8398             :     {
    8399          93 :         sqlite3_result_int(pContext, FALSE);
    8400          93 :         return;
    8401             :     }
    8402          23 :     const double dfMinY = sqlite3_value_double(argv[2]);
    8403          23 :     if (sHeader.MaxY < dfMinY)
    8404             :     {
    8405          11 :         sqlite3_result_int(pContext, FALSE);
    8406          11 :         return;
    8407             :     }
    8408          12 :     const double dfMaxX = sqlite3_value_double(argv[3]);
    8409          12 :     if (sHeader.MinX > dfMaxX)
    8410             :     {
    8411           1 :         sqlite3_result_int(pContext, FALSE);
    8412           1 :         return;
    8413             :     }
    8414          11 :     const double dfMaxY = sqlite3_value_double(argv[4]);
    8415          11 :     sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
    8416             : }
    8417             : 
    8418             : /************************************************************************/
    8419             : /*              OGRGeoPackageSTEnvelopesIntersectsTwoParams()           */
    8420             : /************************************************************************/
    8421             : 
    8422             : static void
    8423           3 : OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
    8424             :                                             sqlite3_value **argv)
    8425             : {
    8426             :     GPkgHeader sHeader;
    8427           3 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
    8428             :     {
    8429           0 :         sqlite3_result_int(pContext, FALSE);
    8430           2 :         return;
    8431             :     }
    8432             :     GPkgHeader sHeader2;
    8433           3 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
    8434             :                                 1))
    8435             :     {
    8436           0 :         sqlite3_result_int(pContext, FALSE);
    8437           0 :         return;
    8438             :     }
    8439           3 :     if (sHeader.MaxX < sHeader2.MinX)
    8440             :     {
    8441           1 :         sqlite3_result_int(pContext, FALSE);
    8442           1 :         return;
    8443             :     }
    8444           2 :     if (sHeader.MaxY < sHeader2.MinY)
    8445             :     {
    8446           0 :         sqlite3_result_int(pContext, FALSE);
    8447           0 :         return;
    8448             :     }
    8449           2 :     if (sHeader.MinX > sHeader2.MaxX)
    8450             :     {
    8451           1 :         sqlite3_result_int(pContext, FALSE);
    8452           1 :         return;
    8453             :     }
    8454           1 :     sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
    8455             : }
    8456             : 
    8457             : /************************************************************************/
    8458             : /*                    OGRGeoPackageGPKGIsAssignable()                   */
    8459             : /************************************************************************/
    8460             : 
    8461           8 : static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
    8462             :                                           int /*argc*/, sqlite3_value **argv)
    8463             : {
    8464          15 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8465           7 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    8466             :     {
    8467           2 :         sqlite3_result_int(pContext, 0);
    8468           2 :         return;
    8469             :     }
    8470             : 
    8471             :     const char *pszExpected =
    8472           6 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8473             :     const char *pszActual =
    8474           6 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8475           6 :     int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
    8476             :                                             OGRFromOGCGeomType(pszExpected));
    8477           6 :     sqlite3_result_int(pContext, bIsAssignable);
    8478             : }
    8479             : 
    8480             : /************************************************************************/
    8481             : /*                     OGRGeoPackageSTSRID()                            */
    8482             : /************************************************************************/
    8483             : 
    8484          12 : static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
    8485             :                                 sqlite3_value **argv)
    8486             : {
    8487             :     GPkgHeader sHeader;
    8488          12 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8489             :     {
    8490           2 :         sqlite3_result_null(pContext);
    8491           2 :         return;
    8492             :     }
    8493          10 :     sqlite3_result_int(pContext, sHeader.iSrsId);
    8494             : }
    8495             : 
    8496             : /************************************************************************/
    8497             : /*                     OGRGeoPackageSetSRID()                           */
    8498             : /************************************************************************/
    8499             : 
    8500          28 : static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
    8501             :                                  sqlite3_value **argv)
    8502             : {
    8503          28 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8504             :     {
    8505           1 :         sqlite3_result_null(pContext);
    8506           1 :         return;
    8507             :     }
    8508          27 :     const int nDestSRID = sqlite3_value_int(argv[1]);
    8509             :     GPkgHeader sHeader;
    8510          27 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8511             :     const GByte *pabyBLOB =
    8512          27 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8513             : 
    8514          54 :     if (nBLOBLen < 8 ||
    8515          27 :         GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
    8516             :     {
    8517             :         // Try also spatialite geometry blobs
    8518           0 :         OGRGeometry *poGeom = nullptr;
    8519           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
    8520             :             OGRERR_NONE)
    8521             :         {
    8522           0 :             sqlite3_result_null(pContext);
    8523           0 :             return;
    8524             :         }
    8525           0 :         size_t nBLOBDestLen = 0;
    8526             :         GByte *pabyDestBLOB =
    8527           0 :             GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
    8528           0 :         if (!pabyDestBLOB)
    8529             :         {
    8530           0 :             sqlite3_result_null(pContext);
    8531           0 :             return;
    8532             :         }
    8533           0 :         sqlite3_result_blob(pContext, pabyDestBLOB,
    8534             :                             static_cast<int>(nBLOBDestLen), VSIFree);
    8535           0 :         return;
    8536             :     }
    8537             : 
    8538          27 :     GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
    8539          27 :     memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
    8540          27 :     int32_t nSRIDToSerialize = nDestSRID;
    8541          27 :     if (OGR_SWAP(sHeader.eByteOrder))
    8542           0 :         nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
    8543          27 :     memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
    8544          27 :     sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
    8545             : }
    8546             : 
    8547             : /************************************************************************/
    8548             : /*                   OGRGeoPackageSTMakeValid()                         */
    8549             : /************************************************************************/
    8550             : 
    8551           3 : static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
    8552             :                                      sqlite3_value **argv)
    8553             : {
    8554           3 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8555             :     {
    8556           2 :         sqlite3_result_null(pContext);
    8557           2 :         return;
    8558             :     }
    8559           1 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8560             :     const GByte *pabyBLOB =
    8561           1 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8562             : 
    8563             :     GPkgHeader sHeader;
    8564           1 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8565             :     {
    8566           0 :         sqlite3_result_null(pContext);
    8567           0 :         return;
    8568             :     }
    8569             : 
    8570             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8571           1 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8572           1 :     if (poGeom == nullptr)
    8573             :     {
    8574             :         // Try also spatialite geometry blobs
    8575           0 :         OGRGeometry *poGeomPtr = nullptr;
    8576           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
    8577             :             OGRERR_NONE)
    8578             :         {
    8579           0 :             sqlite3_result_null(pContext);
    8580           0 :             return;
    8581             :         }
    8582           0 :         poGeom.reset(poGeomPtr);
    8583             :     }
    8584           1 :     auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
    8585           1 :     if (poValid == nullptr)
    8586             :     {
    8587           0 :         sqlite3_result_null(pContext);
    8588           0 :         return;
    8589             :     }
    8590             : 
    8591           1 :     size_t nBLOBDestLen = 0;
    8592           1 :     GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
    8593             :                                               nullptr, &nBLOBDestLen);
    8594           1 :     if (!pabyDestBLOB)
    8595             :     {
    8596           0 :         sqlite3_result_null(pContext);
    8597           0 :         return;
    8598             :     }
    8599           1 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    8600             :                         VSIFree);
    8601             : }
    8602             : 
    8603             : /************************************************************************/
    8604             : /*                   OGRGeoPackageSTArea()                              */
    8605             : /************************************************************************/
    8606             : 
    8607          19 : static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
    8608             :                                 sqlite3_value **argv)
    8609             : {
    8610          19 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8611             :     {
    8612           1 :         sqlite3_result_null(pContext);
    8613          15 :         return;
    8614             :     }
    8615          18 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8616             :     const GByte *pabyBLOB =
    8617          18 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8618             : 
    8619             :     GPkgHeader sHeader;
    8620           0 :     std::unique_ptr<OGRGeometry> poGeom;
    8621          18 :     if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
    8622             :     {
    8623          16 :         if (sHeader.bEmpty)
    8624             :         {
    8625           3 :             sqlite3_result_double(pContext, 0);
    8626          13 :             return;
    8627             :         }
    8628          13 :         const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
    8629          13 :         size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
    8630             :         bool bNeedSwap;
    8631             :         uint32_t nType;
    8632          13 :         if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
    8633             :         {
    8634          13 :             if (nType == wkbPolygon || nType == wkbPolygon25D ||
    8635          11 :                 nType == wkbPolygon + 1000 ||  // wkbPolygonZ
    8636          10 :                 nType == wkbPolygonM || nType == wkbPolygonZM)
    8637             :             {
    8638             :                 double dfArea;
    8639           5 :                 if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
    8640             :                 {
    8641           5 :                     sqlite3_result_double(pContext, dfArea);
    8642           5 :                     return;
    8643           0 :                 }
    8644             :             }
    8645           8 :             else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
    8646           6 :                      nType == wkbMultiPolygon + 1000 ||  // wkbMultiPolygonZ
    8647           5 :                      nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
    8648             :             {
    8649             :                 double dfArea;
    8650           5 :                 if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
    8651             :                 {
    8652           5 :                     sqlite3_result_double(pContext, dfArea);
    8653           5 :                     return;
    8654             :                 }
    8655             :             }
    8656             :         }
    8657             : 
    8658             :         // For curve geometries, fallback to OGRGeometry methods
    8659           3 :         poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8660             :     }
    8661             :     else
    8662             :     {
    8663             :         // Try also spatialite geometry blobs
    8664           2 :         OGRGeometry *poGeomPtr = nullptr;
    8665           2 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
    8666             :             OGRERR_NONE)
    8667             :         {
    8668           1 :             sqlite3_result_null(pContext);
    8669           1 :             return;
    8670             :         }
    8671           1 :         poGeom.reset(poGeomPtr);
    8672             :     }
    8673           4 :     auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
    8674           4 :     if (poSurface == nullptr)
    8675             :     {
    8676           2 :         auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
    8677           2 :         if (poMultiSurface == nullptr)
    8678             :         {
    8679           1 :             sqlite3_result_double(pContext, 0);
    8680             :         }
    8681             :         else
    8682             :         {
    8683           1 :             sqlite3_result_double(pContext, poMultiSurface->get_Area());
    8684             :         }
    8685             :     }
    8686             :     else
    8687             :     {
    8688           2 :         sqlite3_result_double(pContext, poSurface->get_Area());
    8689             :     }
    8690             : }
    8691             : 
    8692             : /************************************************************************/
    8693             : /*                     OGRGeoPackageGeodesicArea()                      */
    8694             : /************************************************************************/
    8695             : 
    8696           5 : static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
    8697             :                                       sqlite3_value **argv)
    8698             : {
    8699           5 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8700             :     {
    8701           1 :         sqlite3_result_null(pContext);
    8702           3 :         return;
    8703             :     }
    8704           4 :     if (sqlite3_value_int(argv[1]) != 1)
    8705             :     {
    8706           2 :         CPLError(CE_Warning, CPLE_NotSupported,
    8707             :                  "ST_Area(geom, use_ellipsoid) is only supported for "
    8708             :                  "use_ellipsoid = 1");
    8709             :     }
    8710             : 
    8711           4 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8712             :     const GByte *pabyBLOB =
    8713           4 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8714             :     GPkgHeader sHeader;
    8715           4 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8716             :     {
    8717           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8718           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8719           1 :         return;
    8720             :     }
    8721             : 
    8722             :     GDALGeoPackageDataset *poDS =
    8723           3 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8724             : 
    8725             :     std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS(
    8726           3 :         poDS->GetSpatialRef(sHeader.iSrsId, true));
    8727           3 :     if (poSrcSRS == nullptr)
    8728             :     {
    8729           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    8730             :                  "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8731           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8732           1 :         return;
    8733             :     }
    8734             : 
    8735             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8736           2 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8737           2 :     if (poGeom == nullptr)
    8738             :     {
    8739             :         // Try also spatialite geometry blobs
    8740           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8741           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8742           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8743             :         {
    8744           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8745           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8746           0 :             return;
    8747             :         }
    8748           0 :         poGeom.reset(poGeomSpatialite);
    8749             :     }
    8750             : 
    8751           2 :     poGeom->assignSpatialReference(poSrcSRS.get());
    8752           2 :     sqlite3_result_double(
    8753             :         pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
    8754             : }
    8755             : 
    8756             : /************************************************************************/
    8757             : /*                   OGRGeoPackageLengthOrGeodesicLength()              */
    8758             : /************************************************************************/
    8759             : 
    8760           8 : static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext,
    8761             :                                                 int argc, sqlite3_value **argv)
    8762             : {
    8763           8 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8764             :     {
    8765           2 :         sqlite3_result_null(pContext);
    8766           5 :         return;
    8767             :     }
    8768           6 :     if (argc == 2 && sqlite3_value_int(argv[1]) != 1)
    8769             :     {
    8770           2 :         CPLError(CE_Warning, CPLE_NotSupported,
    8771             :                  "ST_Length(geom, use_ellipsoid) is only supported for "
    8772             :                  "use_ellipsoid = 1");
    8773             :     }
    8774             : 
    8775           6 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8776             :     const GByte *pabyBLOB =
    8777           6 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8778             :     GPkgHeader sHeader;
    8779           6 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8780             :     {
    8781           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8782           2 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8783           2 :         return;
    8784             :     }
    8785             : 
    8786             :     GDALGeoPackageDataset *poDS =
    8787           4 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8788             : 
    8789           0 :     std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS;
    8790           4 :     if (argc == 2)
    8791             :     {
    8792           3 :         poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
    8793           3 :         if (!poSrcSRS)
    8794             :         {
    8795           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    8796             :                      "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8797           1 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8798           1 :             return;
    8799             :         }
    8800             :     }
    8801             : 
    8802             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8803           3 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8804           3 :     if (poGeom == nullptr)
    8805             :     {
    8806             :         // Try also spatialite geometry blobs
    8807           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8808           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8809           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8810             :         {
    8811           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8812           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8813           0 :             return;
    8814             :         }
    8815           0 :         poGeom.reset(poGeomSpatialite);
    8816             :     }
    8817             : 
    8818           3 :     if (argc == 2)
    8819           2 :         poGeom->assignSpatialReference(poSrcSRS.get());
    8820             : 
    8821           6 :     sqlite3_result_double(
    8822             :         pContext,
    8823           1 :         argc == 1 ? OGR_G_Length(OGRGeometry::ToHandle(poGeom.get()))
    8824           2 :                   : OGR_G_GeodesicLength(OGRGeometry::ToHandle(poGeom.get())));
    8825             : }
    8826             : 
    8827             : /************************************************************************/
    8828             : /*                      OGRGeoPackageTransform()                        */
    8829             : /************************************************************************/
    8830             : 
    8831             : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
    8832             :                             sqlite3_value **argv);
    8833             : 
    8834          32 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
    8835             :                             sqlite3_value **argv)
    8836             : {
    8837          63 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
    8838          31 :         sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
    8839             :     {
    8840           2 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8841          32 :         return;
    8842             :     }
    8843             : 
    8844          30 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8845             :     const GByte *pabyBLOB =
    8846          30 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8847             :     GPkgHeader sHeader;
    8848          30 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8849             :     {
    8850           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8851           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8852           1 :         return;
    8853             :     }
    8854             : 
    8855          29 :     const int nDestSRID = sqlite3_value_int(argv[1]);
    8856          29 :     if (sHeader.iSrsId == nDestSRID)
    8857             :     {
    8858             :         // Return blob unmodified
    8859           3 :         sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
    8860           3 :         return;
    8861             :     }
    8862             : 
    8863             :     GDALGeoPackageDataset *poDS =
    8864          26 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8865             : 
    8866             :     // Try to get the cached coordinate transformation
    8867             :     OGRCoordinateTransformation *poCT;
    8868          26 :     if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
    8869          20 :         poDS->m_nLastCachedCTDstSRId == nDestSRID)
    8870             :     {
    8871          20 :         poCT = poDS->m_poLastCachedCT.get();
    8872             :     }
    8873             :     else
    8874             :     {
    8875             :         std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
    8876           6 :             poSrcSRS(poDS->GetSpatialRef(sHeader.iSrsId, true));
    8877           6 :         if (poSrcSRS == nullptr)
    8878             :         {
    8879           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    8880             :                      "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8881           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8882           0 :             return;
    8883             :         }
    8884             : 
    8885             :         std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
    8886           6 :             poDstSRS(poDS->GetSpatialRef(nDestSRID, true));
    8887           6 :         if (poDstSRS == nullptr)
    8888             :         {
    8889           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
    8890             :                      nDestSRID);
    8891           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8892           0 :             return;
    8893             :         }
    8894             :         poCT =
    8895           6 :             OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get());
    8896           6 :         if (poCT == nullptr)
    8897             :         {
    8898           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8899           0 :             return;
    8900             :         }
    8901             : 
    8902             :         // Cache coordinate transformation for potential later reuse
    8903           6 :         poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
    8904           6 :         poDS->m_nLastCachedCTDstSRId = nDestSRID;
    8905           6 :         poDS->m_poLastCachedCT.reset(poCT);
    8906           6 :         poCT = poDS->m_poLastCachedCT.get();
    8907             :     }
    8908             : 
    8909          26 :     if (sHeader.nHeaderLen >= 8)
    8910             :     {
    8911          26 :         std::vector<GByte> &abyNewBLOB = poDS->m_abyWKBTransformCache;
    8912          26 :         abyNewBLOB.resize(nBLOBLen);
    8913          26 :         memcpy(abyNewBLOB.data(), pabyBLOB, nBLOBLen);
    8914             : 
    8915          26 :         OGREnvelope3D oEnv3d;
    8916          26 :         if (!OGRWKBTransform(abyNewBLOB.data() + sHeader.nHeaderLen,
    8917          26 :                              nBLOBLen - sHeader.nHeaderLen, poCT,
    8918          78 :                              poDS->m_oWKBTransformCache, oEnv3d) ||
    8919          26 :             !GPkgUpdateHeader(abyNewBLOB.data(), nBLOBLen, nDestSRID,
    8920             :                               oEnv3d.MinX, oEnv3d.MaxX, oEnv3d.MinY,
    8921             :                               oEnv3d.MaxY, oEnv3d.MinZ, oEnv3d.MaxZ))
    8922             :         {
    8923           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8924           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8925           0 :             return;
    8926             :         }
    8927             : 
    8928          26 :         sqlite3_result_blob(pContext, abyNewBLOB.data(), nBLOBLen,
    8929             :                             SQLITE_TRANSIENT);
    8930          26 :         return;
    8931             :     }
    8932             : 
    8933             :     // Try also spatialite geometry blobs
    8934           0 :     OGRGeometry *poGeomSpatialite = nullptr;
    8935           0 :     if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8936           0 :                                           &poGeomSpatialite) != OGRERR_NONE)
    8937             :     {
    8938           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8939           0 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8940           0 :         return;
    8941             :     }
    8942           0 :     auto poGeom = std::unique_ptr<OGRGeometry>(poGeomSpatialite);
    8943             : 
    8944           0 :     if (poGeom->transform(poCT) != OGRERR_NONE)
    8945             :     {
    8946           0 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8947           0 :         return;
    8948             :     }
    8949             : 
    8950           0 :     size_t nBLOBDestLen = 0;
    8951             :     GByte *pabyDestBLOB =
    8952           0 :         GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
    8953           0 :     if (!pabyDestBLOB)
    8954             :     {
    8955           0 :         sqlite3_result_null(pContext);
    8956           0 :         return;
    8957             :     }
    8958           0 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    8959             :                         VSIFree);
    8960             : }
    8961             : 
    8962             : /************************************************************************/
    8963             : /*                      OGRGeoPackageSridFromAuthCRS()                  */
    8964             : /************************************************************************/
    8965             : 
    8966           4 : static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
    8967             :                                          int /*argc*/, sqlite3_value **argv)
    8968             : {
    8969           7 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8970           3 :         sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
    8971             :     {
    8972           2 :         sqlite3_result_int(pContext, -1);
    8973           2 :         return;
    8974             :     }
    8975             : 
    8976             :     GDALGeoPackageDataset *poDS =
    8977           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8978             : 
    8979           2 :     char *pszSQL = sqlite3_mprintf(
    8980             :         "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
    8981             :         "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
    8982           2 :         sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
    8983           2 :     OGRErr err = OGRERR_NONE;
    8984           2 :     int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
    8985           2 :     sqlite3_free(pszSQL);
    8986           2 :     if (err != OGRERR_NONE)
    8987           1 :         nSRSId = -1;
    8988           2 :     sqlite3_result_int(pContext, nSRSId);
    8989             : }
    8990             : 
    8991             : /************************************************************************/
    8992             : /*                    OGRGeoPackageImportFromEPSG()                     */
    8993             : /************************************************************************/
    8994             : 
    8995           4 : static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
    8996             :                                         sqlite3_value **argv)
    8997             : {
    8998           4 :     if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
    8999             :     {
    9000           1 :         sqlite3_result_int(pContext, -1);
    9001           2 :         return;
    9002             :     }
    9003             : 
    9004             :     GDALGeoPackageDataset *poDS =
    9005           3 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9006           3 :     OGRSpatialReference oSRS;
    9007           3 :     if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
    9008             :     {
    9009           1 :         sqlite3_result_int(pContext, -1);
    9010           1 :         return;
    9011             :     }
    9012             : 
    9013           2 :     sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
    9014             : }
    9015             : 
    9016             : /************************************************************************/
    9017             : /*               OGRGeoPackageRegisterGeometryExtension()               */
    9018             : /************************************************************************/
    9019             : 
    9020           1 : static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
    9021             :                                                    int /*argc*/,
    9022             :                                                    sqlite3_value **argv)
    9023             : {
    9024           1 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9025           2 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
    9026           1 :         sqlite3_value_type(argv[2]) != SQLITE_TEXT)
    9027             :     {
    9028           0 :         sqlite3_result_int(pContext, 0);
    9029           0 :         return;
    9030             :     }
    9031             : 
    9032             :     const char *pszTableName =
    9033           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9034             :     const char *pszGeomName =
    9035           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9036             :     const char *pszGeomType =
    9037           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
    9038             : 
    9039             :     GDALGeoPackageDataset *poDS =
    9040           1 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9041             : 
    9042           1 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9043           1 :         poDS->GetLayerByName(pszTableName));
    9044           1 :     if (poLyr == nullptr)
    9045             :     {
    9046           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9047           0 :         sqlite3_result_int(pContext, 0);
    9048           0 :         return;
    9049             :     }
    9050           1 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9051             :     {
    9052           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9053           0 :         sqlite3_result_int(pContext, 0);
    9054           0 :         return;
    9055             :     }
    9056           1 :     const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
    9057           1 :     if (eGeomType == wkbUnknown)
    9058             :     {
    9059           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
    9060           0 :         sqlite3_result_int(pContext, 0);
    9061           0 :         return;
    9062             :     }
    9063             : 
    9064           1 :     sqlite3_result_int(
    9065             :         pContext,
    9066           1 :         static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
    9067             : }
    9068             : 
    9069             : /************************************************************************/
    9070             : /*                  OGRGeoPackageCreateSpatialIndex()                   */
    9071             : /************************************************************************/
    9072             : 
    9073          14 : static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
    9074             :                                             int /*argc*/, sqlite3_value **argv)
    9075             : {
    9076          27 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9077          13 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9078             :     {
    9079           2 :         sqlite3_result_int(pContext, 0);
    9080           2 :         return;
    9081             :     }
    9082             : 
    9083             :     const char *pszTableName =
    9084          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9085             :     const char *pszGeomName =
    9086          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9087             :     GDALGeoPackageDataset *poDS =
    9088          12 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9089             : 
    9090          12 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9091          12 :         poDS->GetLayerByName(pszTableName));
    9092          12 :     if (poLyr == nullptr)
    9093             :     {
    9094           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9095           1 :         sqlite3_result_int(pContext, 0);
    9096           1 :         return;
    9097             :     }
    9098          11 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9099             :     {
    9100           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9101           1 :         sqlite3_result_int(pContext, 0);
    9102           1 :         return;
    9103             :     }
    9104             : 
    9105          10 :     sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
    9106             : }
    9107             : 
    9108             : /************************************************************************/
    9109             : /*                  OGRGeoPackageDisableSpatialIndex()                  */
    9110             : /************************************************************************/
    9111             : 
    9112          12 : static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
    9113             :                                              int /*argc*/, sqlite3_value **argv)
    9114             : {
    9115          23 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9116          11 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9117             :     {
    9118           2 :         sqlite3_result_int(pContext, 0);
    9119           2 :         return;
    9120             :     }
    9121             : 
    9122             :     const char *pszTableName =
    9123          10 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9124             :     const char *pszGeomName =
    9125          10 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9126             :     GDALGeoPackageDataset *poDS =
    9127          10 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9128             : 
    9129          10 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9130          10 :         poDS->GetLayerByName(pszTableName));
    9131          10 :     if (poLyr == nullptr)
    9132             :     {
    9133           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9134           1 :         sqlite3_result_int(pContext, 0);
    9135           1 :         return;
    9136             :     }
    9137           9 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9138             :     {
    9139           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9140           1 :         sqlite3_result_int(pContext, 0);
    9141           1 :         return;
    9142             :     }
    9143             : 
    9144           8 :     sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
    9145             : }
    9146             : 
    9147             : /************************************************************************/
    9148             : /*                  OGRGeoPackageHasSpatialIndex()                      */
    9149             : /************************************************************************/
    9150             : 
    9151          29 : static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
    9152             :                                          int /*argc*/, sqlite3_value **argv)
    9153             : {
    9154          57 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9155          28 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9156             :     {
    9157           2 :         sqlite3_result_int(pContext, 0);
    9158           2 :         return;
    9159             :     }
    9160             : 
    9161             :     const char *pszTableName =
    9162          27 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9163             :     const char *pszGeomName =
    9164          27 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9165             :     GDALGeoPackageDataset *poDS =
    9166          27 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9167             : 
    9168          27 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9169          27 :         poDS->GetLayerByName(pszTableName));
    9170          27 :     if (poLyr == nullptr)
    9171             :     {
    9172           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9173           1 :         sqlite3_result_int(pContext, 0);
    9174           1 :         return;
    9175             :     }
    9176          26 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9177             :     {
    9178           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9179           1 :         sqlite3_result_int(pContext, 0);
    9180           1 :         return;
    9181             :     }
    9182             : 
    9183          25 :     poLyr->RunDeferredCreationIfNecessary();
    9184          25 :     poLyr->CreateSpatialIndexIfNecessary();
    9185             : 
    9186          25 :     sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
    9187             : }
    9188             : 
    9189             : /************************************************************************/
    9190             : /*                       GPKG_hstore_get_value()                        */
    9191             : /************************************************************************/
    9192             : 
    9193           4 : static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
    9194             :                                   sqlite3_value **argv)
    9195             : {
    9196           7 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9197           3 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9198             :     {
    9199           2 :         sqlite3_result_null(pContext);
    9200           2 :         return;
    9201             :     }
    9202             : 
    9203             :     const char *pszHStore =
    9204           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9205             :     const char *pszSearchedKey =
    9206           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9207           2 :     char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
    9208           2 :     if (pszValue != nullptr)
    9209           1 :         sqlite3_result_text(pContext, pszValue, -1, CPLFree);
    9210             :     else
    9211           1 :         sqlite3_result_null(pContext);
    9212             : }
    9213             : 
    9214             : /************************************************************************/
    9215             : /*                      GPKG_GDAL_GetMemFileFromBlob()                  */
    9216             : /************************************************************************/
    9217             : 
    9218         105 : static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
    9219             : {
    9220         105 :     int nBytes = sqlite3_value_bytes(argv[0]);
    9221             :     const GByte *pabyBLOB =
    9222         105 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    9223             :     const CPLString osMemFileName(
    9224         105 :         VSIMemGenerateHiddenFilename("GPKG_GDAL_GetMemFileFromBlob"));
    9225         105 :     VSILFILE *fp = VSIFileFromMemBuffer(
    9226             :         osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
    9227         105 :     VSIFCloseL(fp);
    9228         105 :     return osMemFileName;
    9229             : }
    9230             : 
    9231             : /************************************************************************/
    9232             : /*                       GPKG_GDAL_GetMimeType()                        */
    9233             : /************************************************************************/
    9234             : 
    9235          35 : static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
    9236             :                                   sqlite3_value **argv)
    9237             : {
    9238          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9239             :     {
    9240           0 :         sqlite3_result_null(pContext);
    9241           0 :         return;
    9242             :     }
    9243             : 
    9244          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9245             :     GDALDriver *poDriver =
    9246          35 :         GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
    9247          35 :     if (poDriver != nullptr)
    9248             :     {
    9249          35 :         const char *pszRes = nullptr;
    9250          35 :         if (EQUAL(poDriver->GetDescription(), "PNG"))
    9251          23 :             pszRes = "image/png";
    9252          12 :         else if (EQUAL(poDriver->GetDescription(), "JPEG"))
    9253           6 :             pszRes = "image/jpeg";
    9254           6 :         else if (EQUAL(poDriver->GetDescription(), "WEBP"))
    9255           6 :             pszRes = "image/x-webp";
    9256           0 :         else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
    9257           0 :             pszRes = "image/tiff";
    9258             :         else
    9259           0 :             pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
    9260          35 :         sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
    9261             :     }
    9262             :     else
    9263           0 :         sqlite3_result_null(pContext);
    9264          35 :     VSIUnlink(osMemFileName);
    9265             : }
    9266             : 
    9267             : /************************************************************************/
    9268             : /*                       GPKG_GDAL_GetBandCount()                       */
    9269             : /************************************************************************/
    9270             : 
    9271          35 : static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
    9272             :                                    sqlite3_value **argv)
    9273             : {
    9274          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9275             :     {
    9276           0 :         sqlite3_result_null(pContext);
    9277           0 :         return;
    9278             :     }
    9279             : 
    9280          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9281             :     auto poDS = std::unique_ptr<GDALDataset>(
    9282             :         GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    9283          70 :                           nullptr, nullptr, nullptr));
    9284          35 :     if (poDS != nullptr)
    9285             :     {
    9286          35 :         sqlite3_result_int(pContext, poDS->GetRasterCount());
    9287             :     }
    9288             :     else
    9289           0 :         sqlite3_result_null(pContext);
    9290          35 :     VSIUnlink(osMemFileName);
    9291             : }
    9292             : 
    9293             : /************************************************************************/
    9294             : /*                       GPKG_GDAL_HasColorTable()                      */
    9295             : /************************************************************************/
    9296             : 
    9297          35 : static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
    9298             :                                     sqlite3_value **argv)
    9299             : {
    9300          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9301             :     {
    9302           0 :         sqlite3_result_null(pContext);
    9303           0 :         return;
    9304             :     }
    9305             : 
    9306          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9307             :     auto poDS = std::unique_ptr<GDALDataset>(
    9308             :         GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    9309          70 :                           nullptr, nullptr, nullptr));
    9310          35 :     if (poDS != nullptr)
    9311             :     {
    9312          35 :         sqlite3_result_int(
    9313          46 :             pContext, poDS->GetRasterCount() == 1 &&
    9314          11 :                           poDS->GetRasterBand(1)->GetColorTable() != nullptr);
    9315             :     }
    9316             :     else
    9317           0 :         sqlite3_result_null(pContext);
    9318          35 :     VSIUnlink(osMemFileName);
    9319             : }
    9320             : 
    9321             : /************************************************************************/
    9322             : /*                      GetRasterLayerDataset()                         */
    9323             : /************************************************************************/
    9324             : 
    9325             : GDALDataset *
    9326          12 : GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
    9327             : {
    9328          12 :     const auto oIter = m_oCachedRasterDS.find(pszLayerName);
    9329          12 :     if (oIter != m_oCachedRasterDS.end())
    9330          10 :         return oIter->second.get();
    9331             : 
    9332             :     auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
    9333           4 :         (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
    9334           4 :         GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
    9335           2 :     if (!poDS)
    9336             :     {
    9337           0 :         return nullptr;
    9338             :     }
    9339           2 :     m_oCachedRasterDS[pszLayerName] = std::move(poDS);
    9340           2 :     return m_oCachedRasterDS[pszLayerName].get();
    9341             : }
    9342             : 
    9343             : /************************************************************************/
    9344             : /*                   GPKG_gdal_get_layer_pixel_value()                  */
    9345             : /************************************************************************/
    9346             : 
    9347             : // NOTE: keep in sync implementations in ogrsqlitesqlfunctionscommon.cpp
    9348             : // and ogrgeopackagedatasource.cpp
    9349          13 : static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext, int argc,
    9350             :                                             sqlite3_value **argv)
    9351             : {
    9352          13 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
    9353             :     {
    9354           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    9355             :                  "Invalid arguments to gdal_get_layer_pixel_value()");
    9356           1 :         sqlite3_result_null(pContext);
    9357           1 :         return;
    9358             :     }
    9359             : 
    9360             :     const char *pszLayerName =
    9361          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9362             : 
    9363             :     GDALGeoPackageDataset *poGlobalDS =
    9364          12 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9365          12 :     auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
    9366          12 :     if (!poDS)
    9367             :     {
    9368           0 :         sqlite3_result_null(pContext);
    9369           0 :         return;
    9370             :     }
    9371             : 
    9372          12 :     OGRSQLite_gdal_get_pixel_value_common("gdal_get_layer_pixel_value",
    9373             :                                           pContext, argc, argv, poDS);
    9374             : }
    9375             : 
    9376             : /************************************************************************/
    9377             : /*                       GPKG_ogr_layer_Extent()                        */
    9378             : /************************************************************************/
    9379             : 
    9380           3 : static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
    9381             :                                   sqlite3_value **argv)
    9382             : {
    9383           3 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
    9384             :     {
    9385           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
    9386             :                  "ogr_layer_Extent");
    9387           1 :         sqlite3_result_null(pContext);
    9388           2 :         return;
    9389             :     }
    9390             : 
    9391             :     const char *pszLayerName =
    9392           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9393             :     GDALGeoPackageDataset *poDS =
    9394           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9395           2 :     OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
    9396           2 :     if (!poLayer)
    9397             :     {
    9398           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
    9399             :                  "ogr_layer_Extent");
    9400           1 :         sqlite3_result_null(pContext);
    9401           1 :         return;
    9402             :     }
    9403             : 
    9404           1 :     if (poLayer->GetGeomType() == wkbNone)
    9405             :     {
    9406           0 :         sqlite3_result_null(pContext);
    9407           0 :         return;
    9408             :     }
    9409             : 
    9410           1 :     OGREnvelope sExtent;
    9411           1 :     if (poLayer->GetExtent(&sExtent) != OGRERR_NONE)
    9412             :     {
    9413           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
    9414             :                  "ogr_layer_Extent");
    9415           0 :         sqlite3_result_null(pContext);
    9416           0 :         return;
    9417             :     }
    9418             : 
    9419           1 :     OGRPolygon oPoly;
    9420           1 :     OGRLinearRing *poRing = new OGRLinearRing();
    9421           1 :     oPoly.addRingDirectly(poRing);
    9422           1 :     poRing->addPoint(sExtent.MinX, sExtent.MinY);
    9423           1 :     poRing->addPoint(sExtent.MaxX, sExtent.MinY);
    9424           1 :     poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
    9425           1 :     poRing->addPoint(sExtent.MinX, sExtent.MaxY);
    9426           1 :     poRing->addPoint(sExtent.MinX, sExtent.MinY);
    9427             : 
    9428           1 :     const auto poSRS = poLayer->GetSpatialRef();
    9429           1 :     const int nSRID = poDS->GetSrsId(poSRS);
    9430           1 :     size_t nBLOBDestLen = 0;
    9431             :     GByte *pabyDestBLOB =
    9432           1 :         GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
    9433           1 :     if (!pabyDestBLOB)
    9434             :     {
    9435           0 :         sqlite3_result_null(pContext);
    9436           0 :         return;
    9437             :     }
    9438           1 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    9439             :                         VSIFree);
    9440             : }
    9441             : 
    9442             : /************************************************************************/
    9443             : /*                      InstallSQLFunctions()                           */
    9444             : /************************************************************************/
    9445             : 
    9446             : #ifndef SQLITE_DETERMINISTIC
    9447             : #define SQLITE_DETERMINISTIC 0
    9448             : #endif
    9449             : 
    9450             : #ifndef SQLITE_INNOCUOUS
    9451             : #define SQLITE_INNOCUOUS 0
    9452             : #endif
    9453             : 
    9454             : #ifndef UTF8_INNOCUOUS
    9455             : #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
    9456             : #endif
    9457             : 
    9458        1996 : void GDALGeoPackageDataset::InstallSQLFunctions()
    9459             : {
    9460        1996 :     InitSpatialite();
    9461             : 
    9462             :     // Enable SpatiaLite 4.3 "amphibious" mode, i.e. that SpatiaLite functions
    9463             :     // that take geometries will accept GPKG encoded geometries without
    9464             :     // explicit conversion.
    9465             :     // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
    9466             :     // error.
    9467        1996 :     sqlite3_exec(hDB, "SELECT EnableGpkgAmphibiousMode()", nullptr, nullptr,
    9468             :                  nullptr);
    9469             : 
    9470             :     /* Used by RTree Spatial Index Extension */
    9471        1996 :     sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
    9472             :                             OGRGeoPackageSTMinX, nullptr, nullptr);
    9473        1996 :     sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
    9474             :                             OGRGeoPackageSTMinY, nullptr, nullptr);
    9475        1996 :     sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
    9476             :                             OGRGeoPackageSTMaxX, nullptr, nullptr);
    9477        1996 :     sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
    9478             :                             OGRGeoPackageSTMaxY, nullptr, nullptr);
    9479        1996 :     sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
    9480             :                             OGRGeoPackageSTIsEmpty, nullptr, nullptr);
    9481             : 
    9482             :     /* Used by Geometry Type Triggers Extension */
    9483        1996 :     sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
    9484             :                             OGRGeoPackageSTGeometryType, nullptr, nullptr);
    9485        1996 :     sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
    9486             :                             nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
    9487             :                             nullptr);
    9488             : 
    9489             :     /* Used by Geometry SRS ID Triggers Extension */
    9490        1996 :     sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
    9491             :                             OGRGeoPackageSTSRID, nullptr, nullptr);
    9492             : 
    9493             :     /* Spatialite-like functions */
    9494        1996 :     sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
    9495             :                             OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
    9496        1996 :     sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
    9497             :                             OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
    9498        1996 :     sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
    9499             :                             OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
    9500             : 
    9501             :     // HSTORE functions
    9502        1996 :     sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
    9503             :                             GPKG_hstore_get_value, nullptr, nullptr);
    9504             : 
    9505             :     // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
    9506        1996 :     sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
    9507             :                             OGRGeoPackageTransform, nullptr, nullptr);
    9508        1996 :     sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
    9509             :                             OGRGeoPackageTransform, nullptr, nullptr);
    9510        1996 :     sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
    9511             :                             OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
    9512             : 
    9513        1996 :     sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
    9514             :                             OGRGeoPackageSTEnvelopesIntersectsTwoParams,
    9515             :                             nullptr, nullptr);
    9516        1996 :     sqlite3_create_function(
    9517             :         hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
    9518             :         OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
    9519             : 
    9520        1996 :     sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
    9521             :                             OGRGeoPackageSTEnvelopesIntersects, nullptr,
    9522             :                             nullptr);
    9523        1996 :     sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
    9524             :                             nullptr, OGRGeoPackageSTEnvelopesIntersects,
    9525             :                             nullptr, nullptr);
    9526             : 
    9527             :     // Implementation that directly hacks the GeoPackage geometry blob header
    9528        1996 :     sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
    9529             :                             OGRGeoPackageSetSRID, nullptr, nullptr);
    9530             : 
    9531             :     // GDAL specific function
    9532        1996 :     sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
    9533             :                             OGRGeoPackageImportFromEPSG, nullptr, nullptr);
    9534             : 
    9535             :     // May be used by ogrmerge.py
    9536        1996 :     sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
    9537             :                             this, OGRGeoPackageRegisterGeometryExtension,
    9538             :                             nullptr, nullptr);
    9539             : 
    9540        1996 :     if (OGRGeometryFactory::haveGEOS())
    9541             :     {
    9542        1996 :         sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
    9543             :                                 OGRGeoPackageSTMakeValid, nullptr, nullptr);
    9544             :     }
    9545             : 
    9546        1996 :     sqlite3_create_function(hDB, "ST_Length", 1, UTF8_INNOCUOUS, nullptr,
    9547             :                             OGRGeoPackageLengthOrGeodesicLength, nullptr,
    9548             :                             nullptr);
    9549        1996 :     sqlite3_create_function(hDB, "ST_Length", 2, UTF8_INNOCUOUS, this,
    9550             :                             OGRGeoPackageLengthOrGeodesicLength, nullptr,
    9551             :                             nullptr);
    9552             : 
    9553        1996 :     sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
    9554             :                             OGRGeoPackageSTArea, nullptr, nullptr);
    9555        1996 :     sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
    9556             :                             OGRGeoPackageGeodesicArea, nullptr, nullptr);
    9557             : 
    9558             :     // Debug functions
    9559        1996 :     if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
    9560             :     {
    9561         417 :         sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
    9562             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9563             :                                 GPKG_GDAL_GetMimeType, nullptr, nullptr);
    9564         417 :         sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
    9565             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9566             :                                 GPKG_GDAL_GetBandCount, nullptr, nullptr);
    9567         417 :         sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
    9568             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9569             :                                 GPKG_GDAL_HasColorTable, nullptr, nullptr);
    9570             :     }
    9571             : 
    9572        1996 :     sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
    9573             :                             this, GPKG_gdal_get_layer_pixel_value, nullptr,
    9574             :                             nullptr);
    9575        1996 :     sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 6, SQLITE_UTF8,
    9576             :                             this, GPKG_gdal_get_layer_pixel_value, nullptr,
    9577             :                             nullptr);
    9578             : 
    9579             :     // Function from VirtualOGR
    9580        1996 :     sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
    9581             :                             GPKG_ogr_layer_Extent, nullptr, nullptr);
    9582             : 
    9583        1996 :     m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
    9584        1996 : }
    9585             : 
    9586             : /************************************************************************/
    9587             : /*                         OpenOrCreateDB()                             */
    9588             : /************************************************************************/
    9589             : 
    9590        1998 : bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
    9591             : {
    9592        1998 :     const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
    9593             :         flags, /*bRegisterOGR2SQLiteExtensions=*/false,
    9594             :         /*bLoadExtensions=*/true);
    9595        1998 :     if (!bSuccess)
    9596           7 :         return false;
    9597             : 
    9598             :     // Turning on recursive_triggers is needed so that DELETE triggers fire
    9599             :     // in a INSERT OR REPLACE statement. In particular this is needed to
    9600             :     // make sure gpkg_ogr_contents.feature_count is properly updated.
    9601        1991 :     SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
    9602             : 
    9603        1991 :     InstallSQLFunctions();
    9604             : 
    9605             :     const char *pszSqlitePragma =
    9606        1991 :         CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
    9607        1991 :     OGRErr eErr = OGRERR_NONE;
    9608           6 :     if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
    9609             :         // Older sqlite versions don't have this pragma
    9610        3988 :         SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
    9611        1991 :         eErr == OGRERR_NONE)
    9612             :     {
    9613        1991 :         bool bNeedsTrustedSchema = false;
    9614             : 
    9615             :         // Current SQLite versions require PRAGMA trusted_schema = 1 to be
    9616             :         // able to use the RTree from triggers, which is only needed when
    9617             :         // modifying the RTree.
    9618        4918 :         if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
    9619        3046 :              (flags & SQLITE_OPEN_CREATE) != 0) &&
    9620        1055 :             OGRSQLiteRTreeRequiresTrustedSchemaOn())
    9621             :         {
    9622        1055 :             bNeedsTrustedSchema = true;
    9623             :         }
    9624             : 
    9625             : #ifdef HAVE_SPATIALITE
    9626             :         // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
    9627         936 :         if (!bNeedsTrustedSchema && HasExtensionsTable() &&
    9628         858 :             SQLGetInteger(
    9629             :                 hDB,
    9630             :                 "SELECT 1 FROM gpkg_extensions WHERE "
    9631             :                 "extension_name ='gdal_spatialite_computed_geom_column'",
    9632           1 :                 nullptr) == 1 &&
    9633        2927 :             SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
    9634             :         {
    9635           1 :             bNeedsTrustedSchema = true;
    9636             :         }
    9637             : #endif
    9638             : 
    9639        1991 :         if (bNeedsTrustedSchema)
    9640             :         {
    9641        1056 :             CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
    9642        1056 :             SQLCommand(hDB, "PRAGMA trusted_schema = 1");
    9643             :         }
    9644             :     }
    9645             : 
    9646             :     const char *pszPreludeStatements =
    9647        1991 :         CSLFetchNameValue(papszOpenOptions, "PRELUDE_STATEMENTS");
    9648        1991 :     if (pszPreludeStatements)
    9649             :     {
    9650           2 :         if (SQLCommand(hDB, pszPreludeStatements) != OGRERR_NONE)
    9651           0 :             return false;
    9652             :     }
    9653             : 
    9654        1991 :     return true;
    9655             : }
    9656             : 
    9657             : /************************************************************************/
    9658             : /*                   GetLayerWithGetSpatialWhereByName()                */
    9659             : /************************************************************************/
    9660             : 
    9661             : std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
    9662          90 : GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
    9663             : {
    9664             :     OGRGeoPackageLayer *poRet =
    9665          90 :         cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
    9666          90 :     return std::pair(poRet, poRet);
    9667             : }
    9668             : 
    9669             : /************************************************************************/
    9670             : /*                       CommitTransaction()                            */
    9671             : /************************************************************************/
    9672             : 
    9673         199 : OGRErr GDALGeoPackageDataset::CommitTransaction()
    9674             : 
    9675             : {
    9676         199 :     if (m_nSoftTransactionLevel == 1)
    9677             :     {
    9678         198 :         FlushMetadata();
    9679         445 :         for (int i = 0; i < m_nLayers; i++)
    9680             :         {
    9681         247 :             m_papoLayers[i]->DoJobAtTransactionCommit();
    9682             :         }
    9683             :     }
    9684             : 
    9685         199 :     return OGRSQLiteBaseDataSource::CommitTransaction();
    9686             : }
    9687             : 
    9688             : /************************************************************************/
    9689             : /*                     RollbackTransaction()                            */
    9690             : /************************************************************************/
    9691             : 
    9692          35 : OGRErr GDALGeoPackageDataset::RollbackTransaction()
    9693             : 
    9694             : {
    9695             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9696          70 :     std::vector<bool> abAddTriggers;
    9697          35 :     std::vector<bool> abTriggersDeletedInTransaction;
    9698             : #endif
    9699          35 :     if (m_nSoftTransactionLevel == 1)
    9700             :     {
    9701          34 :         FlushMetadata();
    9702          70 :         for (int i = 0; i < m_nLayers; i++)
    9703             :         {
    9704             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9705          36 :             abAddTriggers.push_back(
    9706          36 :                 m_papoLayers[i]->GetAddOGRFeatureCountTriggers());
    9707          36 :             abTriggersDeletedInTransaction.push_back(
    9708          36 :                 m_papoLayers[i]
    9709          36 :                     ->GetOGRFeatureCountTriggersDeletedInTransaction());
    9710          36 :             m_papoLayers[i]->SetAddOGRFeatureCountTriggers(false);
    9711             : #endif
    9712          36 :             m_papoLayers[i]->DoJobAtTransactionRollback();
    9713             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9714          36 :             m_papoLayers[i]->DisableFeatureCount();
    9715             : #endif
    9716             :         }
    9717             :     }
    9718             : 
    9719          35 :     const OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
    9720             : 
    9721             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9722          35 :     if (!abAddTriggers.empty())
    9723             :     {
    9724          68 :         for (int i = 0; i < m_nLayers; i++)
    9725             :         {
    9726          36 :             if (abTriggersDeletedInTransaction[i])
    9727             :             {
    9728           7 :                 m_papoLayers[i]->SetOGRFeatureCountTriggersEnabled(true);
    9729             :             }
    9730             :             else
    9731             :             {
    9732          29 :                 m_papoLayers[i]->SetAddOGRFeatureCountTriggers(
    9733          58 :                     abAddTriggers[i]);
    9734             :             }
    9735             :         }
    9736             :     }
    9737             : #endif
    9738          70 :     return eErr;
    9739             : }
    9740             : 
    9741             : /************************************************************************/
    9742             : /*                       GetGeometryTypeString()                        */
    9743             : /************************************************************************/
    9744             : 
    9745             : const char *
    9746        1389 : GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
    9747             : {
    9748        1389 :     const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
    9749        1401 :     if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
    9750          12 :         CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
    9751             :     {
    9752           0 :         pszGPKGGeomType = "GEOMCOLLECTION";
    9753             :     }
    9754        1389 :     return pszGPKGGeomType;
    9755             : }
    9756             : 
    9757             : /************************************************************************/
    9758             : /*                           GetFieldDomainNames()                      */
    9759             : /************************************************************************/
    9760             : 
    9761             : std::vector<std::string>
    9762          10 : GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
    9763             : {
    9764          10 :     if (!HasDataColumnConstraintsTable())
    9765           3 :         return std::vector<std::string>();
    9766             : 
    9767          14 :     std::vector<std::string> oDomainNamesList;
    9768             : 
    9769           7 :     std::unique_ptr<SQLResult> oResultTable;
    9770             :     {
    9771             :         std::string osSQL =
    9772             :             "SELECT DISTINCT constraint_name "
    9773             :             "FROM gpkg_data_column_constraints "
    9774             :             "WHERE constraint_name NOT LIKE '_%_domain_description' "
    9775             :             "ORDER BY constraint_name "
    9776           7 :             "LIMIT 10000"  // to avoid denial of service
    9777             :             ;
    9778           7 :         oResultTable = SQLQuery(hDB, osSQL.c_str());
    9779           7 :         if (!oResultTable)
    9780           0 :             return oDomainNamesList;
    9781             :     }
    9782             : 
    9783           7 :     if (oResultTable->RowCount() == 10000)
    9784             :     {
    9785           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9786             :                  "Number of rows returned for field domain names has been "
    9787             :                  "truncated.");
    9788             :     }
    9789           7 :     else if (oResultTable->RowCount() > 0)
    9790             :     {
    9791           7 :         oDomainNamesList.reserve(oResultTable->RowCount());
    9792          89 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    9793             :         {
    9794          82 :             const char *pszConstraintName = oResultTable->GetValue(0, i);
    9795          82 :             if (!pszConstraintName)
    9796           0 :                 continue;
    9797             : 
    9798          82 :             oDomainNamesList.emplace_back(pszConstraintName);
    9799             :         }
    9800             :     }
    9801             : 
    9802           7 :     return oDomainNamesList;
    9803             : }
    9804             : 
    9805             : /************************************************************************/
    9806             : /*                           GetFieldDomain()                           */
    9807             : /************************************************************************/
    9808             : 
    9809             : const OGRFieldDomain *
    9810         102 : GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
    9811             : {
    9812         102 :     const auto baseRet = GDALDataset::GetFieldDomain(name);
    9813         102 :     if (baseRet)
    9814          42 :         return baseRet;
    9815             : 
    9816          60 :     if (!HasDataColumnConstraintsTable())
    9817           4 :         return nullptr;
    9818             : 
    9819          56 :     const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
    9820          56 :     const char *min_is_inclusive =
    9821          56 :         bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
    9822          56 :     const char *max_is_inclusive =
    9823          56 :         bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
    9824             : 
    9825          56 :     std::unique_ptr<SQLResult> oResultTable;
    9826             :     // Note: for coded domains, we use a little trick by using a dummy
    9827             :     // _{domainname}_domain_description enum that has a single entry whose
    9828             :     // description is the description of the main domain.
    9829             :     {
    9830          56 :         char *pszSQL = sqlite3_mprintf(
    9831             :             "SELECT constraint_type, value, min, %s, "
    9832             :             "max, %s, description, constraint_name "
    9833             :             "FROM gpkg_data_column_constraints "
    9834             :             "WHERE constraint_name IN ('%q', "
    9835             :             "'_%q_domain_description') "
    9836             :             "AND length(constraint_type) < 100 "  // to
    9837             :                                                   // avoid
    9838             :                                                   // denial
    9839             :                                                   // of
    9840             :                                                   // service
    9841             :             "AND (value IS NULL OR length(value) < "
    9842             :             "10000) "  // to avoid denial
    9843             :                        // of service
    9844             :             "AND (description IS NULL OR "
    9845             :             "length(description) < 10000) "  // to
    9846             :                                              // avoid
    9847             :                                              // denial
    9848             :                                              // of
    9849             :                                              // service
    9850             :             "ORDER BY value "
    9851             :             "LIMIT 10000",  // to avoid denial of
    9852             :                             // service
    9853             :             min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
    9854          56 :         oResultTable = SQLQuery(hDB, pszSQL);
    9855          56 :         sqlite3_free(pszSQL);
    9856          56 :         if (!oResultTable)
    9857           0 :             return nullptr;
    9858             :     }
    9859          56 :     if (oResultTable->RowCount() == 0)
    9860             :     {
    9861          15 :         return nullptr;
    9862             :     }
    9863          41 :     if (oResultTable->RowCount() == 10000)
    9864             :     {
    9865           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9866             :                  "Number of rows returned for field domain %s has been "
    9867             :                  "truncated.",
    9868             :                  name.c_str());
    9869             :     }
    9870             : 
    9871             :     // Try to find the field domain data type from fields that implement it
    9872          41 :     int nFieldType = -1;
    9873          41 :     OGRFieldSubType eSubType = OFSTNone;
    9874          41 :     if (HasDataColumnsTable())
    9875             :     {
    9876          36 :         char *pszSQL = sqlite3_mprintf(
    9877             :             "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
    9878             :             "constraint_name = '%q' LIMIT 10",
    9879             :             name.c_str());
    9880          72 :         auto oResultTable2 = SQLQuery(hDB, pszSQL);
    9881          36 :         sqlite3_free(pszSQL);
    9882          36 :         if (oResultTable2 && oResultTable2->RowCount() >= 1)
    9883             :         {
    9884          46 :             for (int iRecord = 0; iRecord < oResultTable2->RowCount();
    9885             :                  iRecord++)
    9886             :             {
    9887          23 :                 const char *pszTableName = oResultTable2->GetValue(0, iRecord);
    9888          23 :                 const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
    9889          23 :                 if (pszTableName == nullptr || pszColumnName == nullptr)
    9890           0 :                     continue;
    9891             :                 OGRLayer *poLayer =
    9892          46 :                     const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
    9893          23 :                         pszTableName);
    9894          23 :                 if (poLayer)
    9895             :                 {
    9896          23 :                     const auto poFDefn = poLayer->GetLayerDefn();
    9897          23 :                     int nIdx = poFDefn->GetFieldIndex(pszColumnName);
    9898          23 :                     if (nIdx >= 0)
    9899             :                     {
    9900          23 :                         const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
    9901          23 :                         const auto eType = poFieldDefn->GetType();
    9902          23 :                         if (nFieldType < 0)
    9903             :                         {
    9904          23 :                             nFieldType = eType;
    9905          23 :                             eSubType = poFieldDefn->GetSubType();
    9906             :                         }
    9907           0 :                         else if ((eType == OFTInteger64 || eType == OFTReal) &&
    9908             :                                  nFieldType == OFTInteger)
    9909             :                         {
    9910             :                             // ok
    9911             :                         }
    9912           0 :                         else if (eType == OFTInteger &&
    9913           0 :                                  (nFieldType == OFTInteger64 ||
    9914             :                                   nFieldType == OFTReal))
    9915             :                         {
    9916           0 :                             nFieldType = OFTInteger;
    9917           0 :                             eSubType = OFSTNone;
    9918             :                         }
    9919           0 :                         else if (nFieldType != eType)
    9920             :                         {
    9921           0 :                             nFieldType = -1;
    9922           0 :                             eSubType = OFSTNone;
    9923           0 :                             break;
    9924             :                         }
    9925             :                     }
    9926             :                 }
    9927             :             }
    9928             :         }
    9929             :     }
    9930             : 
    9931          41 :     std::unique_ptr<OGRFieldDomain> poDomain;
    9932          82 :     std::vector<OGRCodedValue> asValues;
    9933          41 :     bool error = false;
    9934          82 :     CPLString osLastConstraintType;
    9935          41 :     int nFieldTypeFromEnumCode = -1;
    9936          82 :     std::string osConstraintDescription;
    9937          82 :     std::string osDescrConstraintName("_");
    9938          41 :     osDescrConstraintName += name;
    9939          41 :     osDescrConstraintName += "_domain_description";
    9940         100 :     for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
    9941             :     {
    9942          63 :         const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
    9943          63 :         if (pszConstraintType == nullptr)
    9944           0 :             continue;
    9945          63 :         const char *pszValue = oResultTable->GetValue(1, iRecord);
    9946          63 :         const char *pszMin = oResultTable->GetValue(2, iRecord);
    9947             :         const bool bIsMinIncluded =
    9948          63 :             oResultTable->GetValueAsInteger(3, iRecord) == 1;
    9949          63 :         const char *pszMax = oResultTable->GetValue(4, iRecord);
    9950             :         const bool bIsMaxIncluded =
    9951          63 :             oResultTable->GetValueAsInteger(5, iRecord) == 1;
    9952          63 :         const char *pszDescription = oResultTable->GetValue(6, iRecord);
    9953          63 :         const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
    9954             : 
    9955          63 :         if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
    9956             :         {
    9957           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    9958             :                      "Only constraint of type 'enum' can have multiple rows");
    9959           1 :             error = true;
    9960           1 :             break;
    9961             :         }
    9962             : 
    9963          62 :         if (strcmp(pszConstraintType, "enum") == 0)
    9964             :         {
    9965          42 :             if (pszValue == nullptr)
    9966             :             {
    9967           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    9968             :                          "NULL in 'value' column of enumeration");
    9969           1 :                 error = true;
    9970           1 :                 break;
    9971             :             }
    9972          41 :             if (osDescrConstraintName == pszConstraintName)
    9973             :             {
    9974           1 :                 if (pszDescription)
    9975             :                 {
    9976           1 :                     osConstraintDescription = pszDescription;
    9977             :                 }
    9978           1 :                 continue;
    9979             :             }
    9980          40 :             if (asValues.empty())
    9981             :             {
    9982          20 :                 asValues.reserve(oResultTable->RowCount() + 1);
    9983             :             }
    9984             :             OGRCodedValue cv;
    9985             :             // intended: the 'value' column in GPKG is actually the code
    9986          40 :             cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
    9987          40 :             if (cv.pszCode == nullptr)
    9988             :             {
    9989           0 :                 error = true;
    9990           0 :                 break;
    9991             :             }
    9992          40 :             if (pszDescription)
    9993             :             {
    9994          29 :                 cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
    9995          29 :                 if (cv.pszValue == nullptr)
    9996             :                 {
    9997           0 :                     VSIFree(cv.pszCode);
    9998           0 :                     error = true;
    9999           0 :                     break;
   10000             :                 }
   10001             :             }
   10002             :             else
   10003             :             {
   10004          11 :                 cv.pszValue = nullptr;
   10005             :             }
   10006             : 
   10007             :             // If we can't get the data type from field definition, guess it
   10008             :             // from code.
   10009          40 :             if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
   10010             :             {
   10011          18 :                 switch (CPLGetValueType(cv.pszCode))
   10012             :                 {
   10013          13 :                     case CPL_VALUE_INTEGER:
   10014             :                     {
   10015          13 :                         if (nFieldTypeFromEnumCode != OFTReal &&
   10016             :                             nFieldTypeFromEnumCode != OFTInteger64)
   10017             :                         {
   10018           9 :                             const auto nVal = CPLAtoGIntBig(cv.pszCode);
   10019          17 :                             if (nVal < std::numeric_limits<int>::min() ||
   10020           8 :                                 nVal > std::numeric_limits<int>::max())
   10021             :                             {
   10022           3 :                                 nFieldTypeFromEnumCode = OFTInteger64;
   10023             :                             }
   10024             :                             else
   10025             :                             {
   10026           6 :                                 nFieldTypeFromEnumCode = OFTInteger;
   10027             :                             }
   10028             :                         }
   10029          13 :                         break;
   10030             :                     }
   10031             : 
   10032           3 :                     case CPL_VALUE_REAL:
   10033           3 :                         nFieldTypeFromEnumCode = OFTReal;
   10034           3 :                         break;
   10035             : 
   10036           2 :                     case CPL_VALUE_STRING:
   10037           2 :                         nFieldTypeFromEnumCode = OFTString;
   10038           2 :                         break;
   10039             :                 }
   10040             :             }
   10041             : 
   10042          40 :             asValues.emplace_back(cv);
   10043             :         }
   10044          20 :         else if (strcmp(pszConstraintType, "range") == 0)
   10045             :         {
   10046             :             OGRField sMin;
   10047             :             OGRField sMax;
   10048          14 :             OGR_RawField_SetUnset(&sMin);
   10049          14 :             OGR_RawField_SetUnset(&sMax);
   10050          14 :             if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
   10051           8 :                 nFieldType = OFTReal;
   10052          27 :             if (pszMin != nullptr &&
   10053          13 :                 CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
   10054             :             {
   10055          10 :                 if (nFieldType == OFTInteger)
   10056           3 :                     sMin.Integer = atoi(pszMin);
   10057           7 :                 else if (nFieldType == OFTInteger64)
   10058           3 :                     sMin.Integer64 = CPLAtoGIntBig(pszMin);
   10059             :                 else /* if( nFieldType == OFTReal ) */
   10060           4 :                     sMin.Real = CPLAtof(pszMin);
   10061             :             }
   10062          27 :             if (pszMax != nullptr &&
   10063          13 :                 CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
   10064             :             {
   10065          10 :                 if (nFieldType == OFTInteger)
   10066           3 :                     sMax.Integer = atoi(pszMax);
   10067           7 :                 else if (nFieldType == OFTInteger64)
   10068           3 :                     sMax.Integer64 = CPLAtoGIntBig(pszMax);
   10069             :                 else /* if( nFieldType == OFTReal ) */
   10070           4 :                     sMax.Real = CPLAtof(pszMax);
   10071             :             }
   10072          28 :             poDomain.reset(new OGRRangeFieldDomain(
   10073             :                 name, pszDescription ? pszDescription : "",
   10074             :                 static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
   10075          14 :                 bIsMinIncluded, sMax, bIsMaxIncluded));
   10076             :         }
   10077           6 :         else if (strcmp(pszConstraintType, "glob") == 0)
   10078             :         {
   10079           5 :             if (pszValue == nullptr)
   10080             :             {
   10081           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10082             :                          "NULL in 'value' column of glob");
   10083           1 :                 error = true;
   10084           1 :                 break;
   10085             :             }
   10086           4 :             if (nFieldType < 0)
   10087           1 :                 nFieldType = OFTString;
   10088           8 :             poDomain.reset(new OGRGlobFieldDomain(
   10089             :                 name, pszDescription ? pszDescription : "",
   10090           4 :                 static_cast<OGRFieldType>(nFieldType), eSubType, pszValue));
   10091             :         }
   10092             :         else
   10093             :         {
   10094           1 :             CPLError(CE_Failure, CPLE_AppDefined,
   10095             :                      "Unhandled constraint_type: %s", pszConstraintType);
   10096           1 :             error = true;
   10097           1 :             break;
   10098             :         }
   10099             : 
   10100          58 :         osLastConstraintType = pszConstraintType;
   10101             :     }
   10102             : 
   10103          41 :     if (!asValues.empty())
   10104             :     {
   10105          20 :         if (nFieldType < 0)
   10106           9 :             nFieldType = nFieldTypeFromEnumCode;
   10107          20 :         poDomain.reset(
   10108             :             new OGRCodedFieldDomain(name, osConstraintDescription,
   10109             :                                     static_cast<OGRFieldType>(nFieldType),
   10110          20 :                                     eSubType, std::move(asValues)));
   10111             :     }
   10112             : 
   10113          41 :     if (error)
   10114             :     {
   10115           4 :         return nullptr;
   10116             :     }
   10117             : 
   10118          37 :     m_oMapFieldDomains[name] = std::move(poDomain);
   10119          37 :     return GDALDataset::GetFieldDomain(name);
   10120             : }
   10121             : 
   10122             : /************************************************************************/
   10123             : /*                           AddFieldDomain()                           */
   10124             : /************************************************************************/
   10125             : 
   10126          18 : bool GDALGeoPackageDataset::AddFieldDomain(
   10127             :     std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
   10128             : {
   10129          36 :     const std::string domainName(domain->GetName());
   10130          18 :     if (!GetUpdate())
   10131             :     {
   10132           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10133             :                  "AddFieldDomain() not supported on read-only dataset");
   10134           0 :         return false;
   10135             :     }
   10136          18 :     if (GetFieldDomain(domainName) != nullptr)
   10137             :     {
   10138           1 :         failureReason = "A domain of identical name already exists";
   10139           1 :         return false;
   10140             :     }
   10141          17 :     if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
   10142           0 :         return false;
   10143             : 
   10144          17 :     const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
   10145          17 :     const char *min_is_inclusive =
   10146          17 :         bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
   10147          17 :     const char *max_is_inclusive =
   10148          17 :         bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
   10149             : 
   10150          17 :     const auto &osDescription = domain->GetDescription();
   10151          17 :     switch (domain->GetDomainType())
   10152             :     {
   10153          11 :         case OFDT_CODED:
   10154             :         {
   10155             :             const auto poCodedDomain =
   10156          11 :                 cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
   10157          11 :             if (!osDescription.empty())
   10158             :             {
   10159             :                 // We use a little trick by using a dummy
   10160             :                 // _{domainname}_domain_description enum that has a single
   10161             :                 // entry whose description is the description of the main
   10162             :                 // domain.
   10163           1 :                 char *pszSQL = sqlite3_mprintf(
   10164             :                     "INSERT INTO gpkg_data_column_constraints ("
   10165             :                     "constraint_name, constraint_type, value, "
   10166             :                     "min, %s, max, %s, "
   10167             :                     "description) VALUES ("
   10168             :                     "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
   10169             :                     "NULL, %Q)",
   10170             :                     min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10171             :                     osDescription.c_str());
   10172           1 :                 CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
   10173           1 :                 sqlite3_free(pszSQL);
   10174             :             }
   10175          11 :             const auto &enumeration = poCodedDomain->GetEnumeration();
   10176          33 :             for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
   10177             :             {
   10178          22 :                 char *pszSQL = sqlite3_mprintf(
   10179             :                     "INSERT INTO gpkg_data_column_constraints ("
   10180             :                     "constraint_name, constraint_type, value, "
   10181             :                     "min, %s, max, %s, "
   10182             :                     "description) VALUES ("
   10183             :                     "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
   10184             :                     min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10185          22 :                     enumeration[i].pszCode, enumeration[i].pszValue);
   10186          22 :                 bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
   10187          22 :                 sqlite3_free(pszSQL);
   10188          22 :                 if (!ok)
   10189           0 :                     return false;
   10190             :             }
   10191          11 :             break;
   10192             :         }
   10193             : 
   10194           5 :         case OFDT_RANGE:
   10195             :         {
   10196             :             const auto poRangeDomain =
   10197           5 :                 cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
   10198           5 :             const auto eFieldType = poRangeDomain->GetFieldType();
   10199           5 :             if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
   10200             :                 eFieldType != OFTReal)
   10201             :             {
   10202             :                 failureReason = "Only range domains of numeric type are "
   10203           0 :                                 "supported in GeoPackage";
   10204           0 :                 return false;
   10205             :             }
   10206             : 
   10207           5 :             double dfMin = -std::numeric_limits<double>::infinity();
   10208           5 :             double dfMax = std::numeric_limits<double>::infinity();
   10209           5 :             bool bMinIsInclusive = true;
   10210           5 :             const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
   10211           5 :             bool bMaxIsInclusive = true;
   10212           5 :             const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
   10213           5 :             if (eFieldType == OFTInteger)
   10214             :             {
   10215           1 :                 if (!OGR_RawField_IsUnset(&sMin))
   10216           1 :                     dfMin = sMin.Integer;
   10217           1 :                 if (!OGR_RawField_IsUnset(&sMax))
   10218           1 :                     dfMax = sMax.Integer;
   10219             :             }
   10220           4 :             else if (eFieldType == OFTInteger64)
   10221             :             {
   10222           1 :                 if (!OGR_RawField_IsUnset(&sMin))
   10223           1 :                     dfMin = static_cast<double>(sMin.Integer64);
   10224           1 :                 if (!OGR_RawField_IsUnset(&sMax))
   10225           1 :                     dfMax = static_cast<double>(sMax.Integer64);
   10226             :             }
   10227             :             else /* if( eFieldType == OFTReal ) */
   10228             :             {
   10229           3 :                 if (!OGR_RawField_IsUnset(&sMin))
   10230           3 :                     dfMin = sMin.Real;
   10231           3 :                 if (!OGR_RawField_IsUnset(&sMax))
   10232           3 :                     dfMax = sMax.Real;
   10233             :             }
   10234             : 
   10235           5 :             sqlite3_stmt *hInsertStmt = nullptr;
   10236             :             const char *pszSQL =
   10237           5 :                 CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
   10238             :                            "constraint_name, constraint_type, value, "
   10239             :                            "min, %s, max, %s, "
   10240             :                            "description) VALUES ("
   10241             :                            "?, 'range', NULL, ?, ?, ?, ?, ?)",
   10242             :                            min_is_inclusive, max_is_inclusive);
   10243           5 :             if (sqlite3_prepare_v2(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
   10244             :                 SQLITE_OK)
   10245             :             {
   10246           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10247             :                          "failed to prepare SQL: %s", pszSQL);
   10248           0 :                 return false;
   10249             :             }
   10250           5 :             sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
   10251           5 :                               static_cast<int>(domainName.size()),
   10252             :                               SQLITE_TRANSIENT);
   10253           5 :             sqlite3_bind_double(hInsertStmt, 2, dfMin);
   10254           5 :             sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
   10255           5 :             sqlite3_bind_double(hInsertStmt, 4, dfMax);
   10256           5 :             sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
   10257           5 :             if (osDescription.empty())
   10258             :             {
   10259           3 :                 sqlite3_bind_null(hInsertStmt, 6);
   10260             :             }
   10261             :             else
   10262             :             {
   10263           2 :                 sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
   10264           2 :                                   static_cast<int>(osDescription.size()),
   10265             :                                   SQLITE_TRANSIENT);
   10266             :             }
   10267           5 :             const int sqlite_err = sqlite3_step(hInsertStmt);
   10268           5 :             sqlite3_finalize(hInsertStmt);
   10269           5 :             if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
   10270             :             {
   10271           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10272             :                          "failed to execute insertion: %s",
   10273             :                          sqlite3_errmsg(hDB));
   10274           0 :                 return false;
   10275             :             }
   10276             : 
   10277           5 :             break;
   10278             :         }
   10279             : 
   10280           1 :         case OFDT_GLOB:
   10281             :         {
   10282             :             const auto poGlobDomain =
   10283           1 :                 cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
   10284           2 :             char *pszSQL = sqlite3_mprintf(
   10285             :                 "INSERT INTO gpkg_data_column_constraints ("
   10286             :                 "constraint_name, constraint_type, value, "
   10287             :                 "min, %s, max, %s, "
   10288             :                 "description) VALUES ("
   10289             :                 "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
   10290             :                 min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10291           1 :                 poGlobDomain->GetGlob().c_str(),
   10292           2 :                 osDescription.empty() ? nullptr : osDescription.c_str());
   10293           1 :             bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
   10294           1 :             sqlite3_free(pszSQL);
   10295           1 :             if (!ok)
   10296           0 :                 return false;
   10297             : 
   10298           1 :             break;
   10299             :         }
   10300             :     }
   10301             : 
   10302          17 :     m_oMapFieldDomains[domainName] = std::move(domain);
   10303          17 :     return true;
   10304             : }
   10305             : 
   10306             : /************************************************************************/
   10307             : /*                          AddRelationship()                           */
   10308             : /************************************************************************/
   10309             : 
   10310          24 : bool GDALGeoPackageDataset::AddRelationship(
   10311             :     std::unique_ptr<GDALRelationship> &&relationship,
   10312             :     std::string &failureReason)
   10313             : {
   10314          24 :     if (!GetUpdate())
   10315             :     {
   10316           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10317             :                  "AddRelationship() not supported on read-only dataset");
   10318           0 :         return false;
   10319             :     }
   10320             : 
   10321             :     const std::string osRelationshipName = GenerateNameForRelationship(
   10322          24 :         relationship->GetLeftTableName().c_str(),
   10323          24 :         relationship->GetRightTableName().c_str(),
   10324          96 :         relationship->GetRelatedTableType().c_str());
   10325             :     // sanity checks
   10326          24 :     if (GetRelationship(osRelationshipName) != nullptr)
   10327             :     {
   10328           1 :         failureReason = "A relationship of identical name already exists";
   10329           1 :         return false;
   10330             :     }
   10331             : 
   10332          23 :     if (!ValidateRelationship(relationship.get(), failureReason))
   10333             :     {
   10334          14 :         return false;
   10335             :     }
   10336             : 
   10337           9 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
   10338             :     {
   10339           0 :         return false;
   10340             :     }
   10341           9 :     if (!CreateRelationsTableIfNecessary())
   10342             :     {
   10343           0 :         failureReason = "Could not create gpkgext_relations table";
   10344           0 :         return false;
   10345             :     }
   10346           9 :     if (SQLGetInteger(GetDB(),
   10347             :                       "SELECT 1 FROM gpkg_extensions WHERE "
   10348             :                       "table_name = 'gpkgext_relations'",
   10349           9 :                       nullptr) != 1)
   10350             :     {
   10351           4 :         if (OGRERR_NONE !=
   10352           4 :             SQLCommand(
   10353             :                 GetDB(),
   10354             :                 "INSERT INTO gpkg_extensions "
   10355             :                 "(table_name,column_name,extension_name,definition,scope) "
   10356             :                 "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
   10357             :                 "'http://www.geopackage.org/18-000.html', "
   10358             :                 "'read-write')"))
   10359             :         {
   10360             :             failureReason =
   10361           0 :                 "Could not create gpkg_extensions entry for gpkgext_relations";
   10362           0 :             return false;
   10363             :         }
   10364             :     }
   10365             : 
   10366           9 :     const std::string &osLeftTableName = relationship->GetLeftTableName();
   10367           9 :     const std::string &osRightTableName = relationship->GetRightTableName();
   10368           9 :     const auto &aosLeftTableFields = relationship->GetLeftTableFields();
   10369           9 :     const auto &aosRightTableFields = relationship->GetRightTableFields();
   10370             : 
   10371          18 :     std::string osRelatedTableType = relationship->GetRelatedTableType();
   10372           9 :     if (osRelatedTableType.empty())
   10373             :     {
   10374           5 :         osRelatedTableType = "features";
   10375             :     }
   10376             : 
   10377             :     // generate mapping table if not set
   10378          18 :     CPLString osMappingTableName = relationship->GetMappingTableName();
   10379           9 :     if (osMappingTableName.empty())
   10380             :     {
   10381           3 :         int nIndex = 1;
   10382           3 :         osMappingTableName = osLeftTableName + "_" + osRightTableName;
   10383           3 :         while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
   10384             :         {
   10385           0 :             nIndex += 1;
   10386             :             osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
   10387           0 :                                       osRightTableName.c_str(), nIndex);
   10388             :         }
   10389             : 
   10390             :         // determine whether base/related keys are unique
   10391           3 :         bool bBaseKeyIsUnique = false;
   10392             :         {
   10393             :             const std::set<std::string> uniqueBaseFieldsUC =
   10394             :                 SQLGetUniqueFieldUCConstraints(GetDB(),
   10395           6 :                                                osLeftTableName.c_str());
   10396           6 :             if (uniqueBaseFieldsUC.find(
   10397           3 :                     CPLString(aosLeftTableFields[0]).toupper()) !=
   10398           6 :                 uniqueBaseFieldsUC.end())
   10399             :             {
   10400           2 :                 bBaseKeyIsUnique = true;
   10401             :             }
   10402             :         }
   10403           3 :         bool bRelatedKeyIsUnique = false;
   10404             :         {
   10405             :             const std::set<std::string> uniqueRelatedFieldsUC =
   10406             :                 SQLGetUniqueFieldUCConstraints(GetDB(),
   10407           6 :                                                osRightTableName.c_str());
   10408           6 :             if (uniqueRelatedFieldsUC.find(
   10409           3 :                     CPLString(aosRightTableFields[0]).toupper()) !=
   10410           6 :                 uniqueRelatedFieldsUC.end())
   10411             :             {
   10412           2 :                 bRelatedKeyIsUnique = true;
   10413             :             }
   10414             :         }
   10415             : 
   10416             :         // create mapping table
   10417             : 
   10418           3 :         std::string osBaseIdDefinition = "base_id INTEGER";
   10419           3 :         if (bBaseKeyIsUnique)
   10420             :         {
   10421           2 :             char *pszSQL = sqlite3_mprintf(
   10422             :                 " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
   10423             :                 "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
   10424             :                 "DEFERRED",
   10425             :                 osMappingTableName.c_str(), osLeftTableName.c_str(),
   10426           2 :                 aosLeftTableFields[0].c_str());
   10427           2 :             osBaseIdDefinition += pszSQL;
   10428           2 :             sqlite3_free(pszSQL);
   10429             :         }
   10430             : 
   10431           3 :         std::string osRelatedIdDefinition = "related_id INTEGER";
   10432           3 :         if (bRelatedKeyIsUnique)
   10433             :         {
   10434           2 :             char *pszSQL = sqlite3_mprintf(
   10435             :                 " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
   10436             :                 "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
   10437             :                 "DEFERRED",
   10438             :                 osMappingTableName.c_str(), osRightTableName.c_str(),
   10439           2 :                 aosRightTableFields[0].c_str());
   10440           2 :             osRelatedIdDefinition += pszSQL;
   10441           2 :             sqlite3_free(pszSQL);
   10442             :         }
   10443             : 
   10444           3 :         char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
   10445             :                                        "id INTEGER PRIMARY KEY AUTOINCREMENT, "
   10446             :                                        "%s, %s);",
   10447             :                                        osMappingTableName.c_str(),
   10448             :                                        osBaseIdDefinition.c_str(),
   10449             :                                        osRelatedIdDefinition.c_str());
   10450           3 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
   10451           3 :         sqlite3_free(pszSQL);
   10452           3 :         if (eErr != OGRERR_NONE)
   10453             :         {
   10454             :             failureReason =
   10455           0 :                 ("Could not create mapping table " + osMappingTableName)
   10456           0 :                     .c_str();
   10457           0 :             return false;
   10458             :         }
   10459             : 
   10460             :         /*
   10461             :          * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
   10462             :          * The related tables extension explicitly states that the mapping table should only be
   10463             :          * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
   10464             :          * https://github.com/opengeospatial/geopackage/issues/679).
   10465             :          *
   10466             :          * However, if we don't insert the mapping table into gpkg_contents then it is no longer
   10467             :          * visible to some clients (eg ESRI software only allows opening tables that are present
   10468             :          * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
   10469             :          *
   10470             :          * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
   10471             :          */
   10472           3 :         pszSQL = sqlite3_mprintf(
   10473             :             "INSERT INTO gpkg_contents "
   10474             :             "(table_name,data_type,identifier,description,last_change,srs_id) "
   10475             :             "VALUES "
   10476             :             "('%q','attributes','%q','Mapping table for relationship between "
   10477             :             "%q and %q',%s,0)",
   10478             :             osMappingTableName.c_str(), /*table_name*/
   10479             :             osMappingTableName.c_str(), /*identifier*/
   10480             :             osLeftTableName.c_str(),    /*description left table name*/
   10481             :             osRightTableName.c_str(),   /*description right table name*/
   10482           6 :             GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
   10483             : 
   10484             :         // Note -- we explicitly ignore failures here, because hey, we aren't really
   10485             :         // supposed to be adding this table to gpkg_contents anyway!
   10486           3 :         (void)SQLCommand(hDB, pszSQL);
   10487           3 :         sqlite3_free(pszSQL);
   10488             : 
   10489           3 :         pszSQL = sqlite3_mprintf(
   10490             :             "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
   10491             :             osMappingTableName.c_str(), osMappingTableName.c_str());
   10492           3 :         eErr = SQLCommand(hDB, pszSQL);
   10493           3 :         sqlite3_free(pszSQL);
   10494           3 :         if (eErr != OGRERR_NONE)
   10495             :         {
   10496           0 :             failureReason = ("Could not create index for " +
   10497           0 :                              osMappingTableName + " (base_id)")
   10498           0 :                                 .c_str();
   10499           0 :             return false;
   10500             :         }
   10501             : 
   10502           3 :         pszSQL = sqlite3_mprintf(
   10503             :             "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
   10504             :             osMappingTableName.c_str(), osMappingTableName.c_str());
   10505           3 :         eErr = SQLCommand(hDB, pszSQL);
   10506           3 :         sqlite3_free(pszSQL);
   10507           3 :         if (eErr != OGRERR_NONE)
   10508             :         {
   10509           0 :             failureReason = ("Could not create index for " +
   10510           0 :                              osMappingTableName + " (related_id)")
   10511           0 :                                 .c_str();
   10512           0 :             return false;
   10513             :         }
   10514             :     }
   10515             :     else
   10516             :     {
   10517             :         // validate mapping table structure
   10518           6 :         if (OGRGeoPackageTableLayer *poLayer =
   10519           6 :                 cpl::down_cast<OGRGeoPackageTableLayer *>(
   10520           6 :                     GetLayerByName(osMappingTableName)))
   10521             :         {
   10522           4 :             if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
   10523             :             {
   10524             :                 failureReason =
   10525           2 :                     ("Field base_id must exist in " + osMappingTableName)
   10526           1 :                         .c_str();
   10527           1 :                 return false;
   10528             :             }
   10529           3 :             if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
   10530             :             {
   10531             :                 failureReason =
   10532           2 :                     ("Field related_id must exist in " + osMappingTableName)
   10533           1 :                         .c_str();
   10534           1 :                 return false;
   10535             :             }
   10536             :         }
   10537             :         else
   10538             :         {
   10539             :             failureReason =
   10540           2 :                 ("Could not retrieve table " + osMappingTableName).c_str();
   10541           2 :             return false;
   10542             :         }
   10543             :     }
   10544             : 
   10545           5 :     char *pszSQL = sqlite3_mprintf(
   10546             :         "INSERT INTO gpkg_extensions "
   10547             :         "(table_name,column_name,extension_name,definition,scope) "
   10548             :         "VALUES ('%q', NULL, 'gpkg_related_tables', "
   10549             :         "'http://www.geopackage.org/18-000.html', "
   10550             :         "'read-write')",
   10551             :         osMappingTableName.c_str());
   10552           5 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
   10553           5 :     sqlite3_free(pszSQL);
   10554           5 :     if (eErr != OGRERR_NONE)
   10555             :     {
   10556           0 :         failureReason = ("Could not insert mapping table " +
   10557           0 :                          osMappingTableName + " into gpkg_extensions")
   10558           0 :                             .c_str();
   10559           0 :         return false;
   10560             :     }
   10561             : 
   10562          15 :     pszSQL = sqlite3_mprintf(
   10563             :         "INSERT INTO gpkgext_relations "
   10564             :         "(base_table_name,base_primary_column,related_table_name,related_"
   10565             :         "primary_column,relation_name,mapping_table_name) "
   10566             :         "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
   10567           5 :         osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
   10568           5 :         osRightTableName.c_str(), aosRightTableFields[0].c_str(),
   10569             :         osRelatedTableType.c_str(), osMappingTableName.c_str());
   10570           5 :     eErr = SQLCommand(hDB, pszSQL);
   10571           5 :     sqlite3_free(pszSQL);
   10572           5 :     if (eErr != OGRERR_NONE)
   10573             :     {
   10574           0 :         failureReason = "Could not insert relationship into gpkgext_relations";
   10575           0 :         return false;
   10576             :     }
   10577             : 
   10578           5 :     ClearCachedRelationships();
   10579           5 :     LoadRelationships();
   10580           5 :     return true;
   10581             : }
   10582             : 
   10583             : /************************************************************************/
   10584             : /*                         DeleteRelationship()                         */
   10585             : /************************************************************************/
   10586             : 
   10587           4 : bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
   10588             :                                                std::string &failureReason)
   10589             : {
   10590           4 :     if (eAccess != GA_Update)
   10591             :     {
   10592           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10593             :                  "DeleteRelationship() not supported on read-only dataset");
   10594           0 :         return false;
   10595             :     }
   10596             : 
   10597             :     // ensure relationships are up to date before we try to remove one
   10598           4 :     ClearCachedRelationships();
   10599           4 :     LoadRelationships();
   10600             : 
   10601           8 :     std::string osMappingTableName;
   10602             :     {
   10603           4 :         const GDALRelationship *poRelationship = GetRelationship(name);
   10604           4 :         if (poRelationship == nullptr)
   10605             :         {
   10606           1 :             failureReason = "Could not find relationship with name " + name;
   10607           1 :             return false;
   10608             :         }
   10609             : 
   10610           3 :         osMappingTableName = poRelationship->GetMappingTableName();
   10611             :     }
   10612             : 
   10613             :     // DeleteLayerCommon will delete existing relationship objects, so we can't
   10614             :     // refer to poRelationship or any of its members previously obtained here
   10615           3 :     if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
   10616             :     {
   10617             :         failureReason =
   10618           0 :             "Could not remove mapping layer name " + osMappingTableName;
   10619             : 
   10620             :         // relationships may have been left in an inconsistent state -- reload
   10621             :         // them now
   10622           0 :         ClearCachedRelationships();
   10623           0 :         LoadRelationships();
   10624           0 :         return false;
   10625             :     }
   10626             : 
   10627           3 :     ClearCachedRelationships();
   10628           3 :     LoadRelationships();
   10629           3 :     return true;
   10630             : }
   10631             : 
   10632             : /************************************************************************/
   10633             : /*                        UpdateRelationship()                          */
   10634             : /************************************************************************/
   10635             : 
   10636           6 : bool GDALGeoPackageDataset::UpdateRelationship(
   10637             :     std::unique_ptr<GDALRelationship> &&relationship,
   10638             :     std::string &failureReason)
   10639             : {
   10640           6 :     if (eAccess != GA_Update)
   10641             :     {
   10642           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10643             :                  "UpdateRelationship() not supported on read-only dataset");
   10644           0 :         return false;
   10645             :     }
   10646             : 
   10647             :     // ensure relationships are up to date before we try to update one
   10648           6 :     ClearCachedRelationships();
   10649           6 :     LoadRelationships();
   10650             : 
   10651           6 :     const std::string &osRelationshipName = relationship->GetName();
   10652           6 :     const std::string &osLeftTableName = relationship->GetLeftTableName();
   10653           6 :     const std::string &osRightTableName = relationship->GetRightTableName();
   10654           6 :     const std::string &osMappingTableName = relationship->GetMappingTableName();
   10655           6 :     const auto &aosLeftTableFields = relationship->GetLeftTableFields();
   10656           6 :     const auto &aosRightTableFields = relationship->GetRightTableFields();
   10657             : 
   10658             :     // sanity checks
   10659             :     {
   10660             :         const GDALRelationship *poExistingRelationship =
   10661           6 :             GetRelationship(osRelationshipName);
   10662           6 :         if (poExistingRelationship == nullptr)
   10663             :         {
   10664             :             failureReason =
   10665           1 :                 "The relationship should already exist to be updated";
   10666           1 :             return false;
   10667             :         }
   10668             : 
   10669           5 :         if (!ValidateRelationship(relationship.get(), failureReason))
   10670             :         {
   10671           2 :             return false;
   10672             :         }
   10673             : 
   10674             :         // we don't permit changes to the participating tables
   10675           3 :         if (osLeftTableName != poExistingRelationship->GetLeftTableName())
   10676             :         {
   10677           0 :             failureReason = ("Cannot change base table from " +
   10678           0 :                              poExistingRelationship->GetLeftTableName() +
   10679           0 :                              " to " + osLeftTableName)
   10680           0 :                                 .c_str();
   10681           0 :             return false;
   10682             :         }
   10683           3 :         if (osRightTableName != poExistingRelationship->GetRightTableName())
   10684             :         {
   10685           0 :             failureReason = ("Cannot change related table from " +
   10686           0 :                              poExistingRelationship->GetRightTableName() +
   10687           0 :                              " to " + osRightTableName)
   10688           0 :                                 .c_str();
   10689           0 :             return false;
   10690             :         }
   10691           3 :         if (osMappingTableName != poExistingRelationship->GetMappingTableName())
   10692             :         {
   10693           0 :             failureReason = ("Cannot change mapping table from " +
   10694           0 :                              poExistingRelationship->GetMappingTableName() +
   10695           0 :                              " to " + osMappingTableName)
   10696           0 :                                 .c_str();
   10697           0 :             return false;
   10698             :         }
   10699             :     }
   10700             : 
   10701           6 :     std::string osRelatedTableType = relationship->GetRelatedTableType();
   10702           3 :     if (osRelatedTableType.empty())
   10703             :     {
   10704           0 :         osRelatedTableType = "features";
   10705             :     }
   10706             : 
   10707           3 :     char *pszSQL = sqlite3_mprintf(
   10708             :         "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
   10709             :         osMappingTableName.c_str());
   10710           3 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
   10711           3 :     sqlite3_free(pszSQL);
   10712           3 :     if (eErr != OGRERR_NONE)
   10713             :     {
   10714             :         failureReason =
   10715           0 :             "Could not delete old relationship from gpkgext_relations";
   10716           0 :         return false;
   10717             :     }
   10718             : 
   10719           9 :     pszSQL = sqlite3_mprintf(
   10720             :         "INSERT INTO gpkgext_relations "
   10721             :         "(base_table_name,base_primary_column,related_table_name,related_"
   10722             :         "primary_column,relation_name,mapping_table_name) "
   10723             :         "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
   10724           3 :         osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
   10725           3 :         osRightTableName.c_str(), aosRightTableFields[0].c_str(),
   10726             :         osRelatedTableType.c_str(), osMappingTableName.c_str());
   10727           3 :     eErr = SQLCommand(hDB, pszSQL);
   10728           3 :     sqlite3_free(pszSQL);
   10729           3 :     if (eErr != OGRERR_NONE)
   10730             :     {
   10731             :         failureReason =
   10732           0 :             "Could not insert updated relationship into gpkgext_relations";
   10733           0 :         return false;
   10734             :     }
   10735             : 
   10736           3 :     ClearCachedRelationships();
   10737           3 :     LoadRelationships();
   10738           3 :     return true;
   10739             : }
   10740             : 
   10741             : /************************************************************************/
   10742             : /*                    GetSqliteMasterContent()                          */
   10743             : /************************************************************************/
   10744             : 
   10745             : const std::vector<SQLSqliteMasterContent> &
   10746           2 : GDALGeoPackageDataset::GetSqliteMasterContent()
   10747             : {
   10748           2 :     if (m_aoSqliteMasterContent.empty())
   10749             :     {
   10750             :         auto oResultTable =
   10751           2 :             SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
   10752           1 :         if (oResultTable)
   10753             :         {
   10754          58 :             for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
   10755             :             {
   10756         114 :                 SQLSqliteMasterContent row;
   10757          57 :                 const char *pszSQL = oResultTable->GetValue(0, rowCnt);
   10758          57 :                 row.osSQL = pszSQL ? pszSQL : "";
   10759          57 :                 const char *pszType = oResultTable->GetValue(1, rowCnt);
   10760          57 :                 row.osType = pszType ? pszType : "";
   10761          57 :                 const char *pszTableName = oResultTable->GetValue(2, rowCnt);
   10762          57 :                 row.osTableName = pszTableName ? pszTableName : "";
   10763          57 :                 m_aoSqliteMasterContent.emplace_back(std::move(row));
   10764             :             }
   10765             :         }
   10766             :     }
   10767           2 :     return m_aoSqliteMasterContent;
   10768             : }

Generated by: LCOV version 1.14