LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gpkg - ogrgeopackagedatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 4088 4576 89.3 %
Date: 2025-01-18 12:42:00 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         531 : GetTilingScheme(const char *pszName)
      82             : {
      83         531 :     if (EQUAL(pszName, "CUSTOM"))
      84         403 :         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         782 : OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
     192             : {
     193         782 :     CPLAssert(hDB != nullptr);
     194             : 
     195         782 :     const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
     196             :                                                 "PRAGMA user_version = %u",
     197             :                                                 m_nApplicationId,
     198        1564 :                                                 m_nUserVersion));
     199        1564 :     return SQLCommand(hDB, osPragma.c_str());
     200             : }
     201             : 
     202        2261 : bool GDALGeoPackageDataset::CloseDB()
     203             : {
     204        2261 :     OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
     205        2261 :     m_pSQLFunctionData = nullptr;
     206        2261 :     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         742 : static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
     223             :                                      int nEPSGCode)
     224             : {
     225         742 :     CPLPushErrorHandler(CPLQuietErrorHandler);
     226         742 :     const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
     227         742 :     CPLPopErrorHandler();
     228         742 :     CPLErrorReset();
     229         742 :     return eErr;
     230             : }
     231             : 
     232             : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
     233        1122 : GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
     234             :                                      bool bEmitErrorIfNotFound)
     235             : {
     236        1122 :     const auto oIter = m_oMapSrsIdToSrs.find(iSrsId);
     237        1122 :     if (oIter != m_oMapSrsIdToSrs.end())
     238             :     {
     239          77 :         if (oIter->second == nullptr)
     240          31 :             return nullptr;
     241          46 :         oIter->second->Reference();
     242             :         return std::unique_ptr<OGRSpatialReference,
     243          46 :                                OGRSpatialReferenceReleaser>(oIter->second);
     244             :     }
     245             : 
     246        1045 :     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        1850 :     CPLString oSQL;
     271         925 :     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         925 :                 m_bHasDefinition12_063 ? ", definition_12_063" : "",
     276         925 :                 m_bHasEpochColumn ? ", epoch" : "", iSrsId);
     277             : 
     278        1850 :     auto oResult = SQLQuery(hDB, oSQL.c_str());
     279             : 
     280         925 :     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         913 :     const char *pszName = oResult->GetValue(0, 0);
     307         913 :     if (pszName && EQUAL(pszName, "Undefined SRS"))
     308             :     {
     309         369 :         m_oMapSrsIdToSrs[iSrsId] = nullptr;
     310         369 :         return nullptr;
     311             :     }
     312         544 :     const char *pszWkt = oResult->GetValue(1, 0);
     313         544 :     if (pszWkt == nullptr)
     314           0 :         return nullptr;
     315         544 :     const char *pszOrganization = oResult->GetValue(2, 0);
     316         544 :     const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
     317             :     const char *pszWkt2 =
     318         544 :         m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
     319         544 :     if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
     320          73 :         pszWkt = pszWkt2;
     321             :     const char *pszCoordinateEpoch =
     322         544 :         m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
     323             :     const double dfCoordinateEpoch =
     324         544 :         pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
     325             : 
     326         544 :     OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
     327         544 :     poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     328             :     // Try to import first from EPSG code, and then from WKT
     329         544 :     if (!(pszOrganization && pszOrganizationCoordsysID &&
     330         544 :           EQUAL(pszOrganization, "EPSG") &&
     331         525 :           (atoi(pszOrganizationCoordsysID) == iSrsId ||
     332           4 :            (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
     333         525 :           GDALGPKGImportFromEPSG(
     334        1088 :               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         544 :     poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
     346         544 :     poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
     347         544 :     m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
     348         544 :     poSpatialRef->Reference();
     349             :     return std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>(
     350         544 :         poSpatialRef);
     351             : }
     352             : 
     353         244 : const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
     354             : {
     355         244 :     const char *pszName = oSRS.GetName();
     356         244 :     if (pszName)
     357         244 :         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         774 : int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
     530             : {
     531         774 :     const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
     532        1136 :     if (!poSRSIn || poSRSIn->IsEmpty() ||
     533         362 :         (pszName && EQUAL(pszName, "Undefined SRS")))
     534             :     {
     535         414 :         OGRErr err = OGRERR_NONE;
     536         414 :         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         414 :         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         360 :         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         359 :             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         360 :         if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
     586         360 :             return UNDEFINED_CRS_SRS_ID;
     587             : #undef UNDEFINED_CRS_SRS_ID
     588             : #undef XSTRINGIFY
     589             : #undef STRINGIFY
     590           0 :         return -1;
     591             :     }
     592             : 
     593         720 :     std::unique_ptr<OGRSpatialReference> poSRS(poSRSIn->Clone());
     594             : 
     595         360 :     if (poSRS->IsGeographic() || poSRS->IsLocal())
     596             :     {
     597             :         // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
     598         134 :         if (pszName != nullptr && strlen(pszName) > 0)
     599             :         {
     600         134 :             if (EQUAL(pszName, "Undefined geographic SRS"))
     601           2 :                 return 0;
     602             : 
     603         132 :             if (EQUAL(pszName, "Undefined Cartesian SRS"))
     604           1 :                 return -1;
     605             :         }
     606             :     }
     607             : 
     608         357 :     const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     609             : 
     610         357 :     if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0)
     611             :     {
     612             :         // Try to force identify an EPSG code.
     613          24 :         poSRS->AutoIdentifyEPSG();
     614             : 
     615          24 :         pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     616          24 :         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          24 :         poSRS->SetCoordinateEpoch(poSRSIn->GetCoordinateEpoch());
     629             :     }
     630             : 
     631             :     // Check whether the EPSG authority code is already mapped to a
     632             :     // SRS ID.
     633         357 :     char *pszSQL = nullptr;
     634         357 :     int nSRSId = DEFAULT_SRID;
     635         357 :     int nAuthorityCode = 0;
     636         357 :     OGRErr err = OGRERR_NONE;
     637         357 :     bool bCanUseAuthorityCode = false;
     638         357 :     const char *const apszIsSameOptions[] = {
     639             :         "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
     640             :         "IGNORE_COORDINATE_EPOCH=YES", nullptr};
     641         357 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
     642             :     {
     643         333 :         const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
     644         333 :         if (pszAuthorityCode)
     645             :         {
     646         333 :             if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
     647             :             {
     648         333 :                 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         690 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
     663         333 :         poSRSIn->GetCoordinateEpoch() == 0)
     664             :     {
     665             :         pszSQL =
     666         328 :             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         328 :         nSRSId = SQLGetInteger(hDB, pszSQL, &err);
     672         328 :         sqlite3_free(pszSQL);
     673             : 
     674             :         // Got a match? Return it!
     675         328 :         if (OGRERR_NONE == err)
     676             :         {
     677         109 :             auto poRefSRS = GetSpatialRef(nSRSId);
     678             :             bool bOK =
     679         109 :                 (poRefSRS == nullptr ||
     680         110 :                  poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) ||
     681           1 :                  !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
     682         109 :             if (bOK)
     683             :             {
     684         108 :                 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         249 :     CPLCharUniquePtr pszWKT1;
     702         249 :     CPLCharUniquePtr pszWKT2_2015;
     703         249 :     CPLCharUniquePtr pszWKT2_2019;
     704         249 :     const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
     705         249 :     const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
     706         249 :     const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
     707             : 
     708         498 :     std::string osEpochTest;
     709         249 :     if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
     710             :     {
     711             :         osEpochTest =
     712           3 :             CPLSPrintf(" AND epoch = %.17g", poSRSIn->GetCoordinateEpoch());
     713             :     }
     714             : 
     715         249 :     if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3))
     716             :     {
     717         242 :         char *pszTmp = nullptr;
     718         242 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
     719         242 :         pszWKT1.reset(pszTmp);
     720         242 :         if (pszWKT1 && pszWKT1.get()[0] == '\0')
     721             :         {
     722           0 :             pszWKT1.reset();
     723             :         }
     724             :     }
     725             :     {
     726         249 :         char *pszTmp = nullptr;
     727         249 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
     728         249 :         pszWKT2_2015.reset(pszTmp);
     729         249 :         if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
     730             :         {
     731           0 :             pszWKT2_2015.reset();
     732             :         }
     733             :     }
     734             :     {
     735         249 :         char *pszTmp = nullptr;
     736         249 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
     737         249 :         pszWKT2_2019.reset(pszTmp);
     738         249 :         if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
     739             :         {
     740           0 :             pszWKT2_2019.reset();
     741             :         }
     742             :     }
     743             : 
     744         249 :     if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
     745             :     {
     746           0 :         return DEFAULT_SRID;
     747             :     }
     748             : 
     749         249 :     if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
     750             :     {
     751             :         // Search if there is already an existing entry with this WKT
     752         246 :         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         206 :         else if (pszWKT1)
     775             :         {
     776             :             pszSQL =
     777         204 :                 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         246 :         if (pszSQL)
     786             :         {
     787         244 :             nSRSId = SQLGetInteger(hDB, pszSQL, &err);
     788         244 :             sqlite3_free(pszSQL);
     789         244 :             if (OGRERR_NONE == err)
     790             :             {
     791           5 :                 return nSRSId;
     792             :             }
     793             :         }
     794             :     }
     795             : 
     796         466 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
     797         222 :         poSRSIn->GetCoordinateEpoch() == 0)
     798             :     {
     799         218 :         bool bTryToReuseSRSId = true;
     800         218 :         if (EQUAL(pszAuthorityName, "EPSG"))
     801             :         {
     802         434 :             OGRSpatialReference oSRS_EPSG;
     803         217 :             if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
     804             :                 OGRERR_NONE)
     805             :             {
     806         218 :                 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         218 :         if (bTryToReuseSRSId)
     824             :         {
     825             :             // No match, but maybe we can use the nAuthorityCode as the nSRSId?
     826         217 :             pszSQL = sqlite3_mprintf(
     827             :                 "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
     828             :                 "srs_id = %d",
     829             :                 nAuthorityCode);
     830             : 
     831             :             // Yep, we can!
     832         217 :             if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
     833         216 :                 bCanUseAuthorityCode = true;
     834         217 :             sqlite3_free(pszSQL);
     835             :         }
     836             :     }
     837             : 
     838         244 :     bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
     839         244 :     bool bForceEpoch = false;
     840         246 :     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         244 :     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         249 :     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         244 :     if (bCanUseAuthorityCode)
     893             :     {
     894         216 :         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          28 :         const int nMaxSRSId = SQLGetInteger(
     901             :             hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
     902          28 :         nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
     903             :     }
     904             : 
     905         488 :     std::string osEpochColumn;
     906         244 :     std::string osEpochVal;
     907         244 :     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         244 :     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         202 :         if (pszAuthorityName != nullptr && nAuthorityCode > 0)
     951             :         {
     952         380 :             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         190 :                 GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
     957         380 :                 pszWKT1 ? pszWKT1.get() : "undefined");
     958             :         }
     959             :         else
     960             :         {
     961          24 :             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          12 :                 GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
     966          24 :                 pszWKT1 ? pszWKT1.get() : "undefined");
     967             :         }
     968             :     }
     969             : 
     970             :     // Add new row to gpkg_spatial_ref_sys.
     971         244 :     CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
     972             : 
     973             :     // Free everything that was allocated.
     974         244 :     sqlite3_free(pszSQL);
     975             : 
     976         244 :     return nSRSId;
     977             : }
     978             : 
     979             : /************************************************************************/
     980             : /*                        GDALGeoPackageDataset()                       */
     981             : /************************************************************************/
     982             : 
     983        2250 : 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        2250 :       m_bMapTableToContentsBuilt(false)
     998             : {
     999        2250 :     m_adfGeoTransform[0] = 0.0;
    1000        2250 :     m_adfGeoTransform[1] = 1.0;
    1001        2250 :     m_adfGeoTransform[2] = 0.0;
    1002        2250 :     m_adfGeoTransform[3] = 0.0;
    1003        2250 :     m_adfGeoTransform[4] = 0.0;
    1004        2250 :     m_adfGeoTransform[5] = 1.0;
    1005        2250 : }
    1006             : 
    1007             : /************************************************************************/
    1008             : /*                       ~GDALGeoPackageDataset()                       */
    1009             : /************************************************************************/
    1010             : 
    1011        4500 : GDALGeoPackageDataset::~GDALGeoPackageDataset()
    1012             : {
    1013        2250 :     GDALGeoPackageDataset::Close();
    1014        4500 : }
    1015             : 
    1016             : /************************************************************************/
    1017             : /*                              Close()                                 */
    1018             : /************************************************************************/
    1019             : 
    1020        3751 : CPLErr GDALGeoPackageDataset::Close()
    1021             : {
    1022        3751 :     CPLErr eErr = CE_None;
    1023        3751 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
    1024             :     {
    1025        1307 :         if (eAccess == GA_Update && m_poParentDS == nullptr &&
    1026        3557 :             !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        2250 :         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        4042 :         for (int i = 0; i < nBands; i++)
    1041        1792 :             delete papoBands[i];
    1042        2250 :         nBands = 0;
    1043        2250 :         CPLFree(papoBands);
    1044        2250 :         papoBands = nullptr;
    1045             : 
    1046             :         // Destroy overviews before cleaning m_hTempDB as they could still
    1047             :         // need it
    1048        2575 :         for (int i = 0; i < m_nOverviewCount; i++)
    1049         325 :             delete m_papoOverviewDS[i];
    1050             : 
    1051        2250 :         if (m_poParentDS)
    1052             :         {
    1053         325 :             hDB = nullptr;
    1054             :         }
    1055             : 
    1056        5892 :         for (int i = 0; i < m_nLayers; i++)
    1057        3642 :             delete m_papoLayers[i];
    1058             : 
    1059        2250 :         CPLFree(m_papoLayers);
    1060        2250 :         CPLFree(m_papoOverviewDS);
    1061             : 
    1062             :         std::map<int, OGRSpatialReference *>::iterator oIter =
    1063        2250 :             m_oMapSrsIdToSrs.begin();
    1064        3285 :         for (; oIter != m_oMapSrsIdToSrs.end(); ++oIter)
    1065             :         {
    1066        1035 :             OGRSpatialReference *poSRS = oIter->second;
    1067        1035 :             if (poSRS)
    1068         664 :                 poSRS->Release();
    1069             :         }
    1070             : 
    1071        2250 :         if (!CloseDB())
    1072           0 :             eErr = CE_Failure;
    1073             : 
    1074        2250 :         if (OGRSQLiteBaseDataSource::Close() != CE_None)
    1075           0 :             eErr = CE_Failure;
    1076             :     }
    1077        3751 :     return eErr;
    1078             : }
    1079             : 
    1080             : /************************************************************************/
    1081             : /*                         ICanIWriteBlock()                            */
    1082             : /************************************************************************/
    1083             : 
    1084        5684 : bool GDALGeoPackageDataset::ICanIWriteBlock()
    1085             : {
    1086        5684 :     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        5684 :     if (m_pabyCachedTiles == nullptr)
    1095             :     {
    1096           0 :         return false;
    1097             :     }
    1098             : 
    1099        5684 :     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        5684 :     return true;
    1106             : }
    1107             : 
    1108             : /************************************************************************/
    1109             : /*                            IRasterIO()                               */
    1110             : /************************************************************************/
    1111             : 
    1112         121 : 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         121 :     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         121 :     if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
    1128         112 :         nYSize == nBufYSize && nBandCount == nBands &&
    1129         109 :         m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
    1130             :     {
    1131             :         auto poBand =
    1132         105 :             cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
    1133             :         int nBlockXSize, nBlockYSize;
    1134         105 :         poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
    1135         105 :         const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
    1136         105 :         const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
    1137         105 :         const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
    1138         105 :         const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
    1139         259 :         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         121 :     return eErr;
    1165             : }
    1166             : 
    1167             : /************************************************************************/
    1168             : /*                          GetOGRTableLimit()                          */
    1169             : /************************************************************************/
    1170             : 
    1171        3624 : static int GetOGRTableLimit()
    1172             : {
    1173        3624 :     return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
    1174             : }
    1175             : 
    1176             : /************************************************************************/
    1177             : /*                      GetNameTypeMapFromSQliteMaster()                */
    1178             : /************************************************************************/
    1179             : 
    1180             : const std::map<CPLString, CPLString> &
    1181        1123 : GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
    1182             : {
    1183        1123 :     if (!m_oMapNameToType.empty())
    1184         318 :         return m_oMapNameToType;
    1185             : 
    1186             :     CPLString osSQL(
    1187             :         "SELECT name, type FROM sqlite_master WHERE "
    1188             :         "type IN ('view', 'table') OR "
    1189        1610 :         "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
    1190         805 :     const int nTableLimit = GetOGRTableLimit();
    1191         805 :     if (nTableLimit > 0)
    1192             :     {
    1193         805 :         osSQL += " LIMIT ";
    1194         805 :         osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
    1195             :     }
    1196             : 
    1197         805 :     auto oResult = SQLQuery(hDB, osSQL);
    1198         805 :     if (oResult)
    1199             :     {
    1200       13491 :         for (int i = 0; i < oResult->RowCount(); i++)
    1201             :         {
    1202       12686 :             const char *pszName = oResult->GetValue(0, i);
    1203       12686 :             const char *pszType = oResult->GetValue(1, i);
    1204       12686 :             m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
    1205             :         }
    1206             :     }
    1207             : 
    1208         805 :     return m_oMapNameToType;
    1209             : }
    1210             : 
    1211             : /************************************************************************/
    1212             : /*                    RemoveTableFromSQLiteMasterCache()                */
    1213             : /************************************************************************/
    1214             : 
    1215          53 : void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
    1216             :     const char *pszTableName)
    1217             : {
    1218          53 :     m_oMapNameToType.erase(CPLString(pszTableName).toupper());
    1219          53 : }
    1220             : 
    1221             : /************************************************************************/
    1222             : /*                  GetUnknownExtensionsTableSpecific()                 */
    1223             : /************************************************************************/
    1224             : 
    1225             : const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
    1226         769 : GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
    1227             : {
    1228         769 :     if (m_bMapTableToExtensionsBuilt)
    1229          79 :         return m_oMapTableToExtensions;
    1230         690 :     m_bMapTableToExtensionsBuilt = true;
    1231             : 
    1232         690 :     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        1304 :         ")");
    1255         652 :     const int nTableLimit = GetOGRTableLimit();
    1256         652 :     if (nTableLimit > 0)
    1257             :     {
    1258         652 :         osSQL += " LIMIT ";
    1259         652 :         osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
    1260             :     }
    1261             : 
    1262         652 :     auto oResult = SQLQuery(hDB, osSQL);
    1263         652 :     if (oResult)
    1264             :     {
    1265        1269 :         for (int i = 0; i < oResult->RowCount(); i++)
    1266             :         {
    1267         617 :             const char *pszTableName = oResult->GetValue(0, i);
    1268         617 :             const char *pszExtensionName = oResult->GetValue(1, i);
    1269         617 :             const char *pszDefinition = oResult->GetValue(2, i);
    1270         617 :             const char *pszScope = oResult->GetValue(3, i);
    1271         617 :             if (pszTableName && pszExtensionName && pszDefinition && pszScope)
    1272             :             {
    1273         617 :                 GPKGExtensionDesc oDesc;
    1274         617 :                 oDesc.osExtensionName = pszExtensionName;
    1275         617 :                 oDesc.osDefinition = pszDefinition;
    1276         617 :                 oDesc.osScope = pszScope;
    1277        1234 :                 m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
    1278         617 :                     .push_back(oDesc);
    1279             :             }
    1280             :         }
    1281             :     }
    1282             : 
    1283         652 :     return m_oMapTableToExtensions;
    1284             : }
    1285             : 
    1286             : /************************************************************************/
    1287             : /*                           GetContents()                              */
    1288             : /************************************************************************/
    1289             : 
    1290             : const std::map<CPLString, GPKGContentsDesc> &
    1291         752 : GDALGeoPackageDataset::GetContents()
    1292             : {
    1293         752 :     if (m_bMapTableToContentsBuilt)
    1294          64 :         return m_oMapTableToContents;
    1295         688 :     m_bMapTableToContentsBuilt = true;
    1296             : 
    1297             :     CPLString osSQL("SELECT table_name, data_type, identifier, "
    1298             :                     "description, min_x, min_y, max_x, max_y "
    1299        1376 :                     "FROM gpkg_contents");
    1300         688 :     const int nTableLimit = GetOGRTableLimit();
    1301         688 :     if (nTableLimit > 0)
    1302             :     {
    1303         688 :         osSQL += " LIMIT ";
    1304         688 :         osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1305             :     }
    1306             : 
    1307         688 :     auto oResult = SQLQuery(hDB, osSQL);
    1308         688 :     if (oResult)
    1309             :     {
    1310        1483 :         for (int i = 0; i < oResult->RowCount(); i++)
    1311             :         {
    1312         795 :             const char *pszTableName = oResult->GetValue(0, i);
    1313         795 :             if (pszTableName == nullptr)
    1314           0 :                 continue;
    1315         795 :             const char *pszDataType = oResult->GetValue(1, i);
    1316         795 :             const char *pszIdentifier = oResult->GetValue(2, i);
    1317         795 :             const char *pszDescription = oResult->GetValue(3, i);
    1318         795 :             const char *pszMinX = oResult->GetValue(4, i);
    1319         795 :             const char *pszMinY = oResult->GetValue(5, i);
    1320         795 :             const char *pszMaxX = oResult->GetValue(6, i);
    1321         795 :             const char *pszMaxY = oResult->GetValue(7, i);
    1322         795 :             GPKGContentsDesc oDesc;
    1323         795 :             if (pszDataType)
    1324         795 :                 oDesc.osDataType = pszDataType;
    1325         795 :             if (pszIdentifier)
    1326         795 :                 oDesc.osIdentifier = pszIdentifier;
    1327         795 :             if (pszDescription)
    1328         794 :                 oDesc.osDescription = pszDescription;
    1329         795 :             if (pszMinX)
    1330         558 :                 oDesc.osMinX = pszMinX;
    1331         795 :             if (pszMinY)
    1332         558 :                 oDesc.osMinY = pszMinY;
    1333         795 :             if (pszMaxX)
    1334         558 :                 oDesc.osMaxX = pszMaxX;
    1335         795 :             if (pszMaxY)
    1336         558 :                 oDesc.osMaxY = pszMaxY;
    1337        1590 :             m_oMapTableToContents[CPLString(pszTableName).toupper()] =
    1338        1590 :                 std::move(oDesc);
    1339             :         }
    1340             :     }
    1341             : 
    1342         688 :     return m_oMapTableToContents;
    1343             : }
    1344             : 
    1345             : /************************************************************************/
    1346             : /*                                Open()                                */
    1347             : /************************************************************************/
    1348             : 
    1349        1119 : int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
    1350             :                                 const std::string &osFilenameInZip)
    1351             : {
    1352        1119 :     m_osFilenameInZip = osFilenameInZip;
    1353        1119 :     CPLAssert(m_nLayers == 0);
    1354        1119 :     CPLAssert(hDB == nullptr);
    1355             : 
    1356        1119 :     SetDescription(poOpenInfo->pszFilename);
    1357        2238 :     CPLString osFilename(poOpenInfo->pszFilename);
    1358        2238 :     CPLString osSubdatasetTableName;
    1359             :     GByte abyHeaderLetMeHerePlease[100];
    1360        1119 :     const GByte *pabyHeader = poOpenInfo->pabyHeader;
    1361        1119 :     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         879 :     else if (poOpenInfo->pabyHeader &&
    1407         879 :              STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1408             :                          "SQLite format 3"))
    1409             :     {
    1410         873 :         m_bCallUndeclareFileNotToOpen = true;
    1411         873 :         GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
    1412             :                                          poOpenInfo->nHeaderBytes);
    1413             :     }
    1414             : 
    1415        1119 :     eAccess = poOpenInfo->eAccess;
    1416        1119 :     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        1118 :         m_pszFilename = CPLStrdup(osFilename);
    1424             :     }
    1425             : 
    1426        1119 :     if (poOpenInfo->papszOpenOptions)
    1427             :     {
    1428          99 :         CSLDestroy(papszOpenOptions);
    1429          99 :         papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    1430             :     }
    1431             : 
    1432             : #ifdef ENABLE_SQL_GPKG_FORMAT
    1433        1119 :     if (poOpenInfo->pabyHeader &&
    1434         879 :         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        1114 :     else if (pabyHeader != nullptr)
    1528             : #endif
    1529             :     {
    1530        1114 :         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         774 :             VSIFCloseL(poOpenInfo->fpL);
    1535         774 :             poOpenInfo->fpL = nullptr;
    1536             :         }
    1537             : 
    1538             :         /* See if we can open the SQLite database */
    1539        1114 :         if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
    1540             :                                         : SQLITE_OPEN_READONLY))
    1541           2 :             return FALSE;
    1542             : 
    1543        1112 :         memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
    1544        1112 :         m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
    1545        1112 :         memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
    1546        1112 :         m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
    1547        1112 :         if (m_nApplicationId == GP10_APPLICATION_ID)
    1548             :         {
    1549           7 :             CPLDebug("GPKG", "GeoPackage v1.0");
    1550             :         }
    1551        1105 :         else if (m_nApplicationId == GP11_APPLICATION_ID)
    1552             :         {
    1553           2 :             CPLDebug("GPKG", "GeoPackage v1.1");
    1554             :         }
    1555        1103 :         else if (m_nApplicationId == GPKG_APPLICATION_ID &&
    1556        1100 :                  m_nUserVersion >= GPKG_1_2_VERSION)
    1557             :         {
    1558        1098 :             CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
    1559        1098 :                      (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        1117 :     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        1117 :     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        1117 :     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        1117 :                       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        1117 :     DetectSpatialRefSysColumns();
    1604             : 
    1605             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    1606        1117 :     if (SQLGetInteger(hDB,
    1607             :                       "SELECT 1 FROM sqlite_master WHERE "
    1608             :                       "name = 'gpkg_ogr_contents' AND type = 'table'",
    1609        1117 :                       nullptr) == 1)
    1610             :     {
    1611        1109 :         m_bHasGPKGOGRContents = true;
    1612             :     }
    1613             : #endif
    1614             : 
    1615        1117 :     CheckUnknownExtensions();
    1616             : 
    1617        1117 :     int bRet = FALSE;
    1618        1117 :     bool bHasGPKGExtRelations = false;
    1619        1117 :     if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
    1620             :     {
    1621         932 :         m_bHasGPKGGeometryColumns =
    1622         932 :             SQLGetInteger(hDB,
    1623             :                           "SELECT 1 FROM sqlite_master WHERE "
    1624             :                           "name = 'gpkg_geometry_columns' AND "
    1625             :                           "type IN ('table', 'view')",
    1626         932 :                           nullptr) == 1;
    1627         932 :         bHasGPKGExtRelations = HasGpkgextRelationsTable();
    1628             :     }
    1629        1117 :     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         931 :             "  WHERE data_type IN ('aspatial', 'attributes') ";
    1655             : 
    1656        1862 :         const char *pszListAllTables = CSLFetchNameValueDef(
    1657         931 :             poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
    1658         931 :         bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
    1659         931 :         if (!bHasASpatialOrAttributes)
    1660             :         {
    1661             :             auto oResultTable =
    1662             :                 SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
    1663         930 :                               "data_type = 'attributes' LIMIT 1");
    1664         930 :             bHasASpatialOrAttributes =
    1665         930 :                 (oResultTable && oResultTable->RowCount() == 1);
    1666             :         }
    1667         931 :         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         931 :         if (EQUAL(pszListAllTables, "YES") ||
    1682         930 :             (!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         872 :                 "gpkg_contents)";
    1699         872 :             if (bHasGPKGExtRelations)
    1700             :             {
    1701             :                 osSQL += " AND lower(name) NOT IN (SELECT "
    1702             :                          "lower(mapping_table_name) FROM "
    1703          13 :                          "gpkgext_relations)";
    1704             :             }
    1705             :         }
    1706         931 :         const int nTableLimit = GetOGRTableLimit();
    1707         931 :         if (nTableLimit > 0)
    1708             :         {
    1709         931 :             osSQL += " LIMIT ";
    1710         931 :             osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1711             :         }
    1712             : 
    1713         931 :         auto oResult = SQLQuery(hDB, osSQL.c_str());
    1714         931 :         if (!oResult)
    1715             :         {
    1716           0 :             return FALSE;
    1717             :         }
    1718             : 
    1719         931 :         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         931 :         if (oResult->RowCount() > 0)
    1730             :         {
    1731         818 :             bRet = TRUE;
    1732             : 
    1733        1636 :             m_papoLayers = static_cast<OGRGeoPackageTableLayer **>(CPLMalloc(
    1734         818 :                 sizeof(OGRGeoPackageTableLayer *) * oResult->RowCount()));
    1735             : 
    1736        1636 :             std::map<std::string, int> oMapTableRefCount;
    1737        3815 :             for (int i = 0; i < oResult->RowCount(); i++)
    1738             :             {
    1739        2997 :                 const char *pszTableName = oResult->GetValue(0, i);
    1740        2997 :                 if (pszTableName == nullptr)
    1741           0 :                     continue;
    1742        2997 :                 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        1636 :             std::set<std::string> oExistingLayers;
    1754        3815 :             for (int i = 0; i < oResult->RowCount(); i++)
    1755             :             {
    1756        2997 :                 const char *pszTableName = oResult->GetValue(0, i);
    1757        2997 :                 if (pszTableName == nullptr)
    1758           2 :                     continue;
    1759             :                 const bool bTableHasSeveralGeomColumns =
    1760        2997 :                     oMapTableRefCount[pszTableName] > 1;
    1761        2997 :                 bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
    1762        2997 :                 const char *pszGeomColName = oResult->GetValue(3, i);
    1763        2997 :                 const char *pszGeomType = oResult->GetValue(4, i);
    1764        2997 :                 const char *pszZ = oResult->GetValue(5, i);
    1765        2997 :                 const char *pszM = oResult->GetValue(6, i);
    1766             :                 bool bIsInGpkgContents =
    1767        2997 :                     CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
    1768        2997 :                 if (!bIsInGpkgContents)
    1769          44 :                     m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
    1770        2997 :                 const char *pszObjectType = oResult->GetValue(12, i);
    1771        2997 :                 if (pszObjectType == nullptr ||
    1772        2996 :                     !(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        5666 :                     pszGeomColName ? std::string(pszTableName) + " (" +
    1789             :                                          pszGeomColName + ')'
    1790        5992 :                                    : std::string(pszTableName);
    1791        2996 :                 if (cpl::contains(oExistingLayers, osLayerNameWithGeomColName))
    1792           1 :                     continue;
    1793        2995 :                 oExistingLayers.insert(osLayerNameWithGeomColName);
    1794             :                 const std::string osLayerName = bTableHasSeveralGeomColumns
    1795             :                                                     ? osLayerNameWithGeomColName
    1796        2995 :                                                     : std::string(pszTableName);
    1797             :                 OGRGeoPackageTableLayer *poLayer =
    1798        2995 :                     new OGRGeoPackageTableLayer(this, osLayerName.c_str());
    1799        2995 :                 bool bHasZ = pszZ && atoi(pszZ) > 0;
    1800        2995 :                 bool bHasM = pszM && atoi(pszM) > 0;
    1801        2995 :                 if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
    1802             :                 {
    1803         570 :                     if (pszZ && atoi(pszZ) == 2)
    1804           7 :                         bHasZ = false;
    1805         570 :                     if (pszM && atoi(pszM) == 2)
    1806           6 :                         bHasM = false;
    1807             :                 }
    1808        2995 :                 poLayer->SetOpeningParameters(
    1809             :                     pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
    1810             :                     pszGeomColName, pszGeomType, bHasZ, bHasM);
    1811        2995 :                 m_papoLayers[m_nLayers++] = poLayer;
    1812             :             }
    1813             :         }
    1814             :     }
    1815             : 
    1816        1117 :     bool bHasTileMatrixSet = false;
    1817        1117 :     if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
    1818             :     {
    1819         549 :         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        1117 :     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         548 :             "data_type IN ('tiles', '2d-gridded-coverage')";
    1834         548 :         if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
    1835             :             osSubdatasetTableName =
    1836           2 :                 CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
    1837         548 :         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         548 :         const int nTableLimit = GetOGRTableLimit();
    1846         548 :         if (nTableLimit > 0)
    1847             :         {
    1848         548 :             osSQL += " LIMIT ";
    1849         548 :             osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1850             :         }
    1851             : 
    1852         548 :         auto oResult = SQLQuery(hDB, osSQL.c_str());
    1853         548 :         if (!oResult)
    1854             :         {
    1855           0 :             return FALSE;
    1856             :         }
    1857             : 
    1858         548 :         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         547 :         else if (oResult->RowCount() == 1)
    1865             :         {
    1866         271 :             const char *pszTableName = oResult->GetValue(0, 0);
    1867         271 :             const char *pszIdentifier = oResult->GetValue(1, 0);
    1868         271 :             const char *pszDescription = oResult->GetValue(2, 0);
    1869         271 :             const char *pszSRSId = oResult->GetValue(3, 0);
    1870         271 :             const char *pszMinX = oResult->GetValue(4, 0);
    1871         271 :             const char *pszMinY = oResult->GetValue(5, 0);
    1872         271 :             const char *pszMaxX = oResult->GetValue(6, 0);
    1873         271 :             const char *pszMaxY = oResult->GetValue(7, 0);
    1874         271 :             const char *pszTMSMinX = oResult->GetValue(8, 0);
    1875         271 :             const char *pszTMSMinY = oResult->GetValue(9, 0);
    1876         271 :             const char *pszTMSMaxX = oResult->GetValue(10, 0);
    1877         271 :             const char *pszTMSMaxY = oResult->GetValue(11, 0);
    1878         271 :             const char *pszDataType = oResult->GetValue(12, 0);
    1879         271 :             if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
    1880             :                 pszTMSMaxY)
    1881             :             {
    1882         542 :                 bRet = OpenRaster(
    1883             :                     pszTableName, pszIdentifier, pszDescription,
    1884         271 :                     pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
    1885             :                     CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
    1886             :                     CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
    1887         271 :                     EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
    1888             :             }
    1889             :         }
    1890         276 :         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        1117 :     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        1117 :     if (eAccess == GA_Update)
    1940             :     {
    1941         218 :         FixupWrongRTreeTrigger();
    1942         218 :         FixupWrongMedataReferenceColumnNameUpdate();
    1943             :     }
    1944             : 
    1945        1117 :     SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    1946             : 
    1947        1117 :     return bRet;
    1948             : }
    1949             : 
    1950             : /************************************************************************/
    1951             : /*                    DetectSpatialRefSysColumns()                      */
    1952             : /************************************************************************/
    1953             : 
    1954        1125 : void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
    1955             : {
    1956             :     // Detect definition_12_063 column
    1957             :     {
    1958        1125 :         sqlite3_stmt *hSQLStmt = nullptr;
    1959        1125 :         int rc = sqlite3_prepare_v2(
    1960             :             hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
    1961             :             &hSQLStmt, nullptr);
    1962        1125 :         if (rc == SQLITE_OK)
    1963             :         {
    1964          80 :             m_bHasDefinition12_063 = true;
    1965          80 :             sqlite3_finalize(hSQLStmt);
    1966             :         }
    1967             :     }
    1968             : 
    1969             :     // Detect epoch column
    1970        1125 :     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        1125 : }
    1983             : 
    1984             : /************************************************************************/
    1985             : /*                    FixupWrongRTreeTrigger()                          */
    1986             : /************************************************************************/
    1987             : 
    1988         218 : void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
    1989             : {
    1990             :     auto oResult = SQLQuery(
    1991             :         hDB,
    1992             :         "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
    1993         218 :         "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
    1994         218 :     if (oResult == nullptr)
    1995           0 :         return;
    1996         218 :     if (oResult->RowCount() > 0)
    1997             :     {
    1998           1 :         CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
    1999             :     }
    2000         220 :     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         218 : 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         218 :              "sql LIKE '%column_nameIS%'");
    2058         218 :     if (oResult == nullptr)
    2059           0 :         return;
    2060         218 :     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         355 : 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         355 :     m_osRasterTable = pszTableName;
    2378         355 :     m_dfTMSMinX = dfMinX;
    2379         355 :     m_dfTMSMaxY = dfMaxY;
    2380             : 
    2381             :     // Despite prior checking, the type might be Binary and
    2382             :     // SQLResultGetValue() not working properly on it
    2383         355 :     int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
    2384         355 :     if (nZoomLevel < 0 || nZoomLevel > 65536)
    2385             :     {
    2386           0 :         return false;
    2387             :     }
    2388         355 :     double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
    2389         355 :     double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
    2390         355 :     if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
    2391             :     {
    2392           0 :         return false;
    2393             :     }
    2394         355 :     int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
    2395         355 :     int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
    2396         355 :     if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
    2397             :         nTileHeight > 65536)
    2398             :     {
    2399           0 :         return false;
    2400             :     }
    2401             :     int nTileMatrixWidth = static_cast<int>(
    2402         710 :         std::min(static_cast<GIntBig>(INT_MAX),
    2403         355 :                  CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
    2404             :     int nTileMatrixHeight = static_cast<int>(
    2405         710 :         std::min(static_cast<GIntBig>(INT_MAX),
    2406         355 :                  CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
    2407         355 :     if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
    2408             :     {
    2409           0 :         return false;
    2410             :     }
    2411             : 
    2412             :     /* Use content bounds in priority over tile_matrix_set bounds */
    2413         355 :     double dfGDALMinX = dfMinX;
    2414         355 :     double dfGDALMinY = dfMinY;
    2415         355 :     double dfGDALMaxX = dfMaxX;
    2416         355 :     double dfGDALMaxY = dfMaxY;
    2417             :     pszContentsMinX =
    2418         355 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
    2419             :     pszContentsMinY =
    2420         355 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
    2421             :     pszContentsMaxX =
    2422         355 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
    2423             :     pszContentsMaxY =
    2424         355 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
    2425         355 :     if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
    2426         355 :         pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
    2427             :     {
    2428         709 :         if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
    2429         354 :             CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
    2430             :         {
    2431         354 :             dfGDALMinX = CPLAtof(pszContentsMinX);
    2432         354 :             dfGDALMinY = CPLAtof(pszContentsMinY);
    2433         354 :             dfGDALMaxX = CPLAtof(pszContentsMaxX);
    2434         354 :             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         355 :     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         355 :     int nBandCount = 0;
    2453             :     const char *pszBAND_COUNT =
    2454         355 :         CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
    2455         355 :     if (poParentDS)
    2456             :     {
    2457          86 :         nBandCount = poParentDS->GetRasterCount();
    2458             :     }
    2459         269 :     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         204 :         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         135 :             GetMetadata("IMAGE_STRUCTURE");
    2480         135 :             nBandCount = m_nBandCountFromMetadata;
    2481         135 :             if (nBandCount == 1)
    2482          38 :                 m_eTF = GPKG_TF_PNG;
    2483             :         }
    2484         204 :         if (nBandCount == 1 && !m_osTFFromMetadata.empty())
    2485             :         {
    2486           2 :             m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
    2487             :         }
    2488         204 :         if (nBandCount <= 0 || nBandCount > 4)
    2489          83 :             nBandCount = 4;
    2490             :     }
    2491             : 
    2492         355 :     return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
    2493             :                       dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
    2494             :                       nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
    2495         355 :                       dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
    2496             : }
    2497             : 
    2498             : /************************************************************************/
    2499             : /*                      ComputeTileAndPixelShifts()                     */
    2500             : /************************************************************************/
    2501             : 
    2502         765 : bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
    2503             : {
    2504             :     int nTileWidth, nTileHeight;
    2505         765 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    2506             : 
    2507             :     // Compute shift between GDAL origin and TileMatrixSet origin
    2508         765 :     const double dfShiftXPixels =
    2509         765 :         (m_adfGeoTransform[0] - m_dfTMSMinX) / m_adfGeoTransform[1];
    2510         765 :     if (dfShiftXPixels / nTileWidth <= INT_MIN ||
    2511         763 :         dfShiftXPixels / nTileWidth > INT_MAX)
    2512           2 :         return false;
    2513         763 :     const int64_t nShiftXPixels =
    2514         763 :         static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
    2515         763 :     m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
    2516         763 :     if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
    2517          11 :         m_nShiftXTiles--;
    2518         763 :     m_nShiftXPixelsMod =
    2519         763 :         (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
    2520             :         nTileWidth;
    2521             : 
    2522         763 :     const double dfShiftYPixels =
    2523         763 :         (m_adfGeoTransform[3] - m_dfTMSMaxY) / m_adfGeoTransform[5];
    2524         763 :     if (dfShiftYPixels / nTileHeight <= INT_MIN ||
    2525         763 :         dfShiftYPixels / nTileHeight > INT_MAX)
    2526           1 :         return false;
    2527         762 :     const int64_t nShiftYPixels =
    2528         762 :         static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
    2529         762 :     m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
    2530         762 :     if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
    2531          11 :         m_nShiftYTiles--;
    2532         762 :     m_nShiftYPixelsMod =
    2533         762 :         (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
    2534             :         nTileHeight;
    2535         762 :     return true;
    2536             : }
    2537             : 
    2538             : /************************************************************************/
    2539             : /*                            AllocCachedTiles()                        */
    2540             : /************************************************************************/
    2541             : 
    2542         762 : bool GDALGeoPackageDataset::AllocCachedTiles()
    2543             : {
    2544             :     int nTileWidth, nTileHeight;
    2545         762 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    2546             : 
    2547             :     // We currently need 4 caches because of
    2548             :     // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
    2549         762 :     const int nCacheCount = 4;
    2550             :     /*
    2551             :             (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
    2552             :             (GetUpdate() && m_eDT == GDT_Byte) ? 2 : 1;
    2553             :     */
    2554         762 :     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         762 :     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         762 :     return true;
    2566             : }
    2567             : 
    2568             : /************************************************************************/
    2569             : /*                         InitRaster()                                 */
    2570             : /************************************************************************/
    2571             : 
    2572         594 : 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         594 :     m_osRasterTable = pszTableName;
    2580         594 :     m_dfTMSMinX = dfTMSMinX;
    2581         594 :     m_dfTMSMaxY = dfTMSMaxY;
    2582         594 :     m_nZoomLevel = nZoomLevel;
    2583         594 :     m_nTileMatrixWidth = nTileMatrixWidth;
    2584         594 :     m_nTileMatrixHeight = nTileMatrixHeight;
    2585             : 
    2586         594 :     m_bGeoTransformValid = true;
    2587         594 :     m_adfGeoTransform[0] = dfGDALMinX;
    2588         594 :     m_adfGeoTransform[1] = dfPixelXSize;
    2589         594 :     m_adfGeoTransform[3] = dfGDALMaxY;
    2590         594 :     m_adfGeoTransform[5] = -dfPixelYSize;
    2591         594 :     double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
    2592         594 :     double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
    2593         594 :     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         594 :     nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
    2600         594 :     nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
    2601             : 
    2602         594 :     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        2079 :     for (int i = 1; i <= nBandCount; i++)
    2624             :     {
    2625             :         GDALGeoPackageRasterBand *poNewBand =
    2626        1485 :             new GDALGeoPackageRasterBand(this, nTileWidth, nTileHeight);
    2627        1485 :         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        1485 :         SetBand(i, poNewBand);
    2636             : 
    2637        1485 :         if (nBandCount == 1 && m_poCTFromMetadata)
    2638             :         {
    2639           3 :             poNewBand->AssignColorTable(m_poCTFromMetadata.get());
    2640             :         }
    2641        1485 :         if (!m_osNodataValueFromMetadata.empty())
    2642             :         {
    2643           8 :             poNewBand->SetNoDataValueInternal(
    2644             :                 CPLAtof(m_osNodataValueFromMetadata.c_str()));
    2645             :         }
    2646             :     }
    2647             : 
    2648         594 :     if (!ComputeTileAndPixelShifts())
    2649             :     {
    2650           3 :         CPLError(CE_Failure, CPLE_AppDefined,
    2651             :                  "Overflow occurred in ComputeTileAndPixelShifts()");
    2652           3 :         return false;
    2653             :     }
    2654             : 
    2655         591 :     GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    2656         591 :     GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
    2657             :                                     CPLSPrintf("%d", m_nZoomLevel));
    2658             : 
    2659         591 :     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         271 : 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         271 :     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         542 :     CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
    2726         542 :     CPLString osUom;
    2727         542 :     CPLString osFieldName;
    2728         542 :     CPLString osGridCellEncoding;
    2729         271 :     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         271 :     m_bRecordInsertedInGPKGContent = true;
    2859         271 :     m_nSRID = nSRSId;
    2860             : 
    2861         541 :     if (auto poSRS = GetSpatialRef(nSRSId))
    2862             :     {
    2863         270 :         m_oSRS = *(poSRS.get());
    2864             :     }
    2865             : 
    2866             :     /* Various sanity checks added in the SELECT */
    2867         271 :     char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
    2868         542 :     CPLString osQuotedTableName(pszQuotedTableName);
    2869         271 :     sqlite3_free(pszQuotedTableName);
    2870         271 :     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         542 :     CPLString osSQL(pszSQL);
    2884             :     const char *pszZoomLevel =
    2885         271 :         CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
    2886         271 :     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         266 :     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          53 :                        osQuotedTableName.c_str());
    2911             :     }
    2912         271 :     osSQL += " ORDER BY zoom_level DESC";
    2913             :     // To avoid denial of service.
    2914         271 :     osSQL += " LIMIT 100";
    2915             : 
    2916         542 :     auto oResult = SQLQuery(hDB, osSQL.c_str());
    2917         271 :     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         270 :     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         540 :     CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
    2948             : 
    2949         270 :     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         269 :     if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
    2990             :                     pszContentsMinX, pszContentsMinY, pszContentsMaxX,
    2991         269 :                     pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
    2992             :     {
    2993           3 :         return false;
    2994             :     }
    2995             : 
    2996             :     auto poBand =
    2997         266 :         reinterpret_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
    2998         266 :     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         266 :     if (!osUom.empty())
    3031             :     {
    3032           2 :         poBand->SetUnitTypeInternal(osUom);
    3033             :     }
    3034         266 :     if (!osFieldName.empty())
    3035             :     {
    3036          64 :         GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
    3037             :     }
    3038         266 :     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         266 :     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         266 :     const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
    3064         266 :     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         266 :     ParseCompressionOptions(papszOpenOptionsIn);
    3090             : 
    3091         266 :     m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
    3092             : 
    3093             :     // Set metadata
    3094         266 :     if (pszIdentifier && pszIdentifier[0])
    3095         266 :         GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
    3096         266 :     if (pszDescription && pszDescription[0])
    3097          21 :         GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
    3098             : 
    3099             :     // Add overviews
    3100         351 :     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         266 :     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         145 : CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    3144             : {
    3145         145 :     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         144 :     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         143 :     const int nSRID = GetSrsId(poSRS);
    3159         286 :     const auto poTS = GetTilingScheme(m_osTilingScheme);
    3160         143 :     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         142 :     m_nSRID = nSRID;
    3169         142 :     m_oSRS.Clear();
    3170         142 :     if (poSRS)
    3171         141 :         m_oSRS = *poSRS;
    3172             : 
    3173         142 :     if (m_bRecordInsertedInGPKGContent)
    3174             :     {
    3175         117 :         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         117 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
    3179         117 :         sqlite3_free(pszSQL);
    3180         117 :         if (eErr != OGRERR_NONE)
    3181           0 :             return CE_Failure;
    3182             : 
    3183         117 :         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         117 :         eErr = SQLCommand(hDB, pszSQL);
    3187         117 :         sqlite3_free(pszSQL);
    3188         117 :         if (eErr != OGRERR_NONE)
    3189           0 :             return CE_Failure;
    3190             :     }
    3191             : 
    3192         142 :     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         176 : CPLErr GDALGeoPackageDataset::SetGeoTransform(double *padfGeoTransform)
    3213             : {
    3214         176 :     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         174 :     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         173 :     if (m_bGeoTransformValid)
    3227             :     {
    3228           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3229             :                  "Cannot modify geotransform once set");
    3230           1 :         return CE_Failure;
    3231             :     }
    3232         172 :     if (padfGeoTransform[2] != 0.0 || padfGeoTransform[4] != 0 ||
    3233         172 :         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         172 :     if (m_nZoomLevel < 0)
    3241             :     {
    3242         171 :         const auto poTS = GetTilingScheme(m_osTilingScheme);
    3243         171 :         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         171 :     memcpy(m_adfGeoTransform, padfGeoTransform, 6 * sizeof(double));
    3276         171 :     m_bGeoTransformValid = true;
    3277             : 
    3278         171 :     return FinalizeRasterRegistration();
    3279             : }
    3280             : 
    3281             : /************************************************************************/
    3282             : /*                      FinalizeRasterRegistration()                    */
    3283             : /************************************************************************/
    3284             : 
    3285         171 : CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
    3286             : {
    3287             :     OGRErr eErr;
    3288             : 
    3289         171 :     m_dfTMSMinX = m_adfGeoTransform[0];
    3290         171 :     m_dfTMSMaxY = m_adfGeoTransform[3];
    3291             : 
    3292             :     int nTileWidth, nTileHeight;
    3293         171 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3294             : 
    3295         171 :     if (m_nZoomLevel < 0)
    3296             :     {
    3297         151 :         m_nZoomLevel = 0;
    3298         225 :         while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
    3299         151 :                (nRasterYSize >> m_nZoomLevel) > nTileHeight)
    3300          74 :             m_nZoomLevel++;
    3301             :     }
    3302             : 
    3303         171 :     double dfPixelXSizeZoomLevel0 = m_adfGeoTransform[1] * (1 << m_nZoomLevel);
    3304         171 :     double dfPixelYSizeZoomLevel0 =
    3305         171 :         fabs(m_adfGeoTransform[5]) * (1 << m_nZoomLevel);
    3306             :     int nTileXCountZoomLevel0 =
    3307         171 :         std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
    3308             :     int nTileYCountZoomLevel0 =
    3309         171 :         std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
    3310             : 
    3311         342 :     const auto poTS = GetTilingScheme(m_osTilingScheme);
    3312         171 :     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         171 :     m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
    3323         171 :     m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
    3324             : 
    3325         171 :     if (!ComputeTileAndPixelShifts())
    3326             :     {
    3327           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3328             :                  "Overflow occurred in ComputeTileAndPixelShifts()");
    3329           0 :         return CE_Failure;
    3330             :     }
    3331             : 
    3332         171 :     if (!AllocCachedTiles())
    3333             :     {
    3334           0 :         return CE_Failure;
    3335             :     }
    3336             : 
    3337         171 :     double dfGDALMinX = m_adfGeoTransform[0];
    3338         171 :     double dfGDALMinY =
    3339         171 :         m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
    3340         171 :     double dfGDALMaxX =
    3341         171 :         m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
    3342         171 :     double dfGDALMaxY = m_adfGeoTransform[3];
    3343             : 
    3344         171 :     if (SoftStartTransaction() != OGRERR_NONE)
    3345           0 :         return CE_Failure;
    3346             : 
    3347             :     const char *pszCurrentDate =
    3348         171 :         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         342 :         "('%q','%q','%q','%q',%.17g,%.17g,%.17g,%.17g,");
    3354         171 :     osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
    3355         171 :     osInsertGpkgContentsFormatting += ",%d)";
    3356         342 :     char *pszSQL = sqlite3_mprintf(
    3357             :         osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
    3358         171 :         (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         171 :     eErr = SQLCommand(hDB, pszSQL);
    3366         171 :     sqlite3_free(pszSQL);
    3367         171 :     if (eErr != OGRERR_NONE)
    3368             :     {
    3369           0 :         SoftRollbackTransaction();
    3370           0 :         return CE_Failure;
    3371             :     }
    3372             : 
    3373         171 :     double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
    3374             :                                          dfPixelXSizeZoomLevel0;
    3375         171 :     double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
    3376             :                                          dfPixelYSizeZoomLevel0;
    3377             : 
    3378             :     pszSQL =
    3379         171 :         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         171 :     eErr = SQLCommand(hDB, pszSQL);
    3385         171 :     sqlite3_free(pszSQL);
    3386         171 :     if (eErr != OGRERR_NONE)
    3387             :     {
    3388           0 :         SoftRollbackTransaction();
    3389           0 :         return CE_Failure;
    3390             :     }
    3391             : 
    3392         171 :     m_papoOverviewDS = static_cast<GDALGeoPackageDataset **>(
    3393         171 :         CPLCalloc(sizeof(GDALGeoPackageDataset *), m_nZoomLevel));
    3394             : 
    3395         577 :     for (int i = 0; i <= m_nZoomLevel; i++)
    3396             :     {
    3397         406 :         double dfPixelXSizeZoomLevel = 0.0;
    3398         406 :         double dfPixelYSizeZoomLevel = 0.0;
    3399         406 :         int nTileMatrixWidth = 0;
    3400         406 :         int nTileMatrixHeight = 0;
    3401         406 :         if (EQUAL(m_osTilingScheme, "CUSTOM"))
    3402             :         {
    3403         225 :             dfPixelXSizeZoomLevel =
    3404         225 :                 m_adfGeoTransform[1] * (1 << (m_nZoomLevel - i));
    3405         225 :             dfPixelYSizeZoomLevel =
    3406         225 :                 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         406 :         nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
    3414         406 :         nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
    3415             : 
    3416         406 :         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         406 :         eErr = SQLCommand(hDB, pszSQL);
    3425         406 :         sqlite3_free(pszSQL);
    3426         406 :         if (eErr != OGRERR_NONE)
    3427             :         {
    3428           0 :             SoftRollbackTransaction();
    3429           0 :             return CE_Failure;
    3430             :         }
    3431             : 
    3432         406 :         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         171 :     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         171 :     SoftCommitTransaction();
    3459             : 
    3460         171 :     m_nOverviewCount = m_nZoomLevel;
    3461         171 :     m_bRecordInsertedInGPKGContent = true;
    3462             : 
    3463         171 :     return CE_None;
    3464             : }
    3465             : 
    3466             : /************************************************************************/
    3467             : /*                             FlushCache()                             */
    3468             : /************************************************************************/
    3469             : 
    3470        2415 : CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
    3471             : {
    3472        2415 :     if (m_bInFlushCache)
    3473           0 :         return CE_None;
    3474             : 
    3475        2415 :     if (eAccess == GA_Update || !m_bMetadataDirty)
    3476             :     {
    3477        2412 :         SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    3478             :     }
    3479             : 
    3480        2415 :     if (m_bRemoveOGREmptyTable)
    3481             :     {
    3482         586 :         m_bRemoveOGREmptyTable = false;
    3483         586 :         RemoveOGREmptyTable();
    3484             :     }
    3485             : 
    3486        2415 :     CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
    3487             : 
    3488        2415 :     FlushMetadata();
    3489             : 
    3490        2415 :     if (eAccess == GA_Update || !m_bMetadataDirty)
    3491             :     {
    3492             :         // Needed again as above IFlushCacheWithErrCode()
    3493             :         // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
    3494             :         // which modifies metadata
    3495        2415 :         SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    3496             :     }
    3497             : 
    3498        2415 :     return eErr;
    3499             : }
    3500             : 
    3501        4623 : CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
    3502             : 
    3503             : {
    3504        4623 :     if (m_bInFlushCache)
    3505        2141 :         return CE_None;
    3506        2482 :     m_bInFlushCache = true;
    3507        2482 :     if (hDB && eAccess == GA_ReadOnly && bAtClosing)
    3508             :     {
    3509             :         // Clean-up metadata that will go to PAM by removing items that
    3510             :         // are reconstructed.
    3511        1824 :         CPLStringList aosMD;
    3512        1531 :         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         912 :         oMDMD.SetMetadata(aosMD.List());
    3532         912 :         oMDMD.SetMetadata(nullptr, "IMAGE_STRUCTURE");
    3533             : 
    3534        1824 :         GDALPamDataset::FlushCache(bAtClosing);
    3535             :     }
    3536             :     else
    3537             :     {
    3538             :         // Short circuit GDALPamDataset to avoid serialization to .aux.xml
    3539        1570 :         GDALDataset::FlushCache(bAtClosing);
    3540             :     }
    3541             : 
    3542        6229 :     for (int i = 0; i < m_nLayers; i++)
    3543             :     {
    3544        3747 :         m_papoLayers[i]->RunDeferredCreationIfNecessary();
    3545        3747 :         m_papoLayers[i]->CreateSpatialIndexIfNecessary();
    3546             :     }
    3547             : 
    3548             :     // Update raster table last_change column in gpkg_contents if needed
    3549        2482 :     if (m_bHasModifiedTiles)
    3550             :     {
    3551         510 :         for (int i = 1; i <= nBands; ++i)
    3552             :         {
    3553             :             auto poBand =
    3554         341 :                 cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
    3555         341 :             if (!poBand->HaveStatsMetadataBeenSetInThisSession())
    3556             :             {
    3557         334 :                 poBand->InvalidateStatistics();
    3558         334 :                 if (psPam && psPam->pszPamFilename)
    3559         334 :                     VSIUnlink(psPam->pszPamFilename);
    3560             :             }
    3561             :         }
    3562             : 
    3563         169 :         UpdateGpkgContentsLastChange(m_osRasterTable);
    3564             : 
    3565         169 :         m_bHasModifiedTiles = false;
    3566             :     }
    3567             : 
    3568        2482 :     CPLErr eErr = FlushTiles();
    3569             : 
    3570        2482 :     m_bInFlushCache = false;
    3571        2482 :     return eErr;
    3572             : }
    3573             : 
    3574             : /************************************************************************/
    3575             : /*                       GetCurrentDateEscapedSQL()                      */
    3576             : /************************************************************************/
    3577             : 
    3578        1730 : std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
    3579             : {
    3580             :     const char *pszCurrentDate =
    3581        1730 :         CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
    3582        1730 :     if (pszCurrentDate)
    3583           6 :         return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
    3584        1727 :     return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
    3585             : }
    3586             : 
    3587             : /************************************************************************/
    3588             : /*                    UpdateGpkgContentsLastChange()                    */
    3589             : /************************************************************************/
    3590             : 
    3591             : OGRErr
    3592         743 : GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
    3593             : {
    3594             :     char *pszSQL =
    3595         743 :         sqlite3_mprintf("UPDATE gpkg_contents SET "
    3596             :                         "last_change = %s "
    3597             :                         "WHERE lower(table_name) = lower('%q')",
    3598        1486 :                         GetCurrentDateEscapedSQL().c_str(), pszTableName);
    3599         743 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
    3600         743 :     sqlite3_free(pszSQL);
    3601         743 :     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        4819 : const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
    3939             : {
    3940        4991 :     if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
    3941         172 :         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        4815 :     return pszDomain;
    3950             : }
    3951             : 
    3952             : /************************************************************************/
    3953             : /*                           HasMetadataTables()                        */
    3954             : /************************************************************************/
    3955             : 
    3956        4770 : bool GDALGeoPackageDataset::HasMetadataTables() const
    3957             : {
    3958        4770 :     if (m_nHasMetadataTables < 0)
    3959             :     {
    3960             :         const int nCount =
    3961        1822 :             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        1822 :         m_nHasMetadataTables = nCount == 2;
    3967             :     }
    3968        4770 :     return CPL_TO_BOOL(m_nHasMetadataTables);
    3969             : }
    3970             : 
    3971             : /************************************************************************/
    3972             : /*                         HasDataColumnsTable()                        */
    3973             : /************************************************************************/
    3974             : 
    3975         946 : bool GDALGeoPackageDataset::HasDataColumnsTable() const
    3976             : {
    3977        1892 :     const int nCount = SQLGetInteger(
    3978         946 :         hDB,
    3979             :         "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
    3980             :         "AND type IN ('table', 'view')",
    3981             :         nullptr);
    3982         946 :     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        1080 : bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
    4126             : {
    4127        2160 :     const int nCount = SQLGetInteger(
    4128        1080 :         hDB,
    4129             :         "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
    4130             :         "AND type IN ('table', 'view')",
    4131             :         nullptr);
    4132        1080 :     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        3243 : char **GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
    4196             : 
    4197             : {
    4198        3243 :     pszDomain = CheckMetadataDomain(pszDomain);
    4199        3243 :     if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
    4200          60 :         return m_aosSubDatasets.List();
    4201             : 
    4202        3183 :     if (m_bHasReadMetadataFromStorage)
    4203        1411 :         return GDALPamDataset::GetMetadata(pszDomain);
    4204             : 
    4205        1772 :     m_bHasReadMetadataFromStorage = true;
    4206             : 
    4207        1772 :     TryLoadXML();
    4208             : 
    4209        1772 :     if (!HasMetadataTables())
    4210        1290 :         return GDALPamDataset::GetMetadata(pszDomain);
    4211             : 
    4212         482 :     char *pszSQL = nullptr;
    4213         482 :     if (!m_osRasterTable.empty())
    4214             :     {
    4215         169 :         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         313 :         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         964 :     auto oResult = SQLQuery(hDB, pszSQL);
    4239         482 :     sqlite3_free(pszSQL);
    4240         482 :     if (!oResult)
    4241             :     {
    4242           0 :         return GDALPamDataset::GetMetadata(pszDomain);
    4243             :     }
    4244             : 
    4245         482 :     char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
    4246             : 
    4247             :     /* GDAL metadata */
    4248         671 :     for (int i = 0; i < oResult->RowCount(); i++)
    4249             :     {
    4250         189 :         const char *pszMetadata = oResult->GetValue(0, i);
    4251         189 :         const char *pszMDStandardURI = oResult->GetValue(1, i);
    4252         189 :         const char *pszMimeType = oResult->GetValue(2, i);
    4253         189 :         const char *pszReferenceScope = oResult->GetValue(3, i);
    4254         189 :         if (pszMetadata && pszMDStandardURI && pszMimeType &&
    4255         189 :             pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
    4256         173 :             EQUAL(pszMimeType, "text/xml"))
    4257             :         {
    4258         173 :             CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
    4259         173 :             if (psXMLNode)
    4260             :             {
    4261         346 :                 GDALMultiDomainMetadata oLocalMDMD;
    4262         173 :                 oLocalMDMD.XMLInit(psXMLNode, FALSE);
    4263         331 :                 if (!m_osRasterTable.empty() &&
    4264         158 :                     EQUAL(pszReferenceScope, "geopackage"))
    4265             :                 {
    4266           6 :                     oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
    4267             :                 }
    4268             :                 else
    4269             :                 {
    4270             :                     papszMetadata =
    4271         167 :                         CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
    4272         167 :                     CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
    4273         167 :                     CSLConstList papszIter = papszDomainList;
    4274         445 :                     while (papszIter && *papszIter)
    4275             :                     {
    4276         278 :                         if (EQUAL(*papszIter, "IMAGE_STRUCTURE"))
    4277             :                         {
    4278             :                             CSLConstList papszMD =
    4279         125 :                                 oLocalMDMD.GetMetadata(*papszIter);
    4280             :                             const char *pszBAND_COUNT =
    4281         125 :                                 CSLFetchNameValue(papszMD, "BAND_COUNT");
    4282         125 :                             if (pszBAND_COUNT)
    4283         123 :                                 m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
    4284             : 
    4285             :                             const char *pszCOLOR_TABLE =
    4286         125 :                                 CSLFetchNameValue(papszMD, "COLOR_TABLE");
    4287         125 :                             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         125 :                                 CSLFetchNameValue(papszMD, "TILE_FORMAT");
    4317         125 :                             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         125 :                                 CSLFetchNameValue(papszMD, "NODATA_VALUE");
    4327         125 :                             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         278 :                         papszIter++;
    4340             :                     }
    4341             :                 }
    4342         173 :                 CPLDestroyXMLNode(psXMLNode);
    4343             :             }
    4344             :         }
    4345             :     }
    4346             : 
    4347         482 :     GDALPamDataset::SetMetadata(papszMetadata);
    4348         482 :     CSLDestroy(papszMetadata);
    4349         482 :     papszMetadata = nullptr;
    4350             : 
    4351             :     /* Add non-GDAL metadata now */
    4352         482 :     int nNonGDALMDILocal = 1;
    4353         482 :     int nNonGDALMDIGeopackage = 1;
    4354         671 :     for (int i = 0; i < oResult->RowCount(); i++)
    4355             :     {
    4356         189 :         const char *pszMetadata = oResult->GetValue(0, i);
    4357         189 :         const char *pszMDStandardURI = oResult->GetValue(1, i);
    4358         189 :         const char *pszMimeType = oResult->GetValue(2, i);
    4359         189 :         const char *pszReferenceScope = oResult->GetValue(3, i);
    4360         189 :         if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
    4361         189 :             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         189 :         int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
    4369         189 :         if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
    4370         173 :             EQUAL(pszMimeType, "text/xml"))
    4371         173 :             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         482 :     return GDALPamDataset::GetMetadata(pszDomain);
    4398             : }
    4399             : 
    4400             : /************************************************************************/
    4401             : /*                            WriteMetadata()                           */
    4402             : /************************************************************************/
    4403             : 
    4404         666 : void GDALGeoPackageDataset::WriteMetadata(
    4405             :     CPLXMLNode *psXMLNode, /* will be destroyed by the method */
    4406             :     const char *pszTableName)
    4407             : {
    4408         666 :     const bool bIsEmpty = (psXMLNode == nullptr);
    4409         666 :     if (!HasMetadataTables())
    4410             :     {
    4411         483 :         if (bIsEmpty || !CreateMetadataTables())
    4412             :         {
    4413         220 :             CPLDestroyXMLNode(psXMLNode);
    4414         220 :             return;
    4415             :         }
    4416             :     }
    4417             : 
    4418         446 :     char *pszXML = nullptr;
    4419         446 :     if (!bIsEmpty)
    4420             :     {
    4421             :         CPLXMLNode *psMasterXMLNode =
    4422         305 :             CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
    4423         305 :         psMasterXMLNode->psChild = psXMLNode;
    4424         305 :         pszXML = CPLSerializeXMLTree(psMasterXMLNode);
    4425         305 :         CPLDestroyXMLNode(psMasterXMLNode);
    4426             :     }
    4427             :     // cppcheck-suppress uselessAssignmentPtrArg
    4428         446 :     psXMLNode = nullptr;
    4429             : 
    4430         446 :     char *pszSQL = nullptr;
    4431         446 :     if (pszTableName && pszTableName[0] != '\0')
    4432             :     {
    4433         313 :         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         133 :         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         446 :     int mdId = SQLGetInteger(hDB, pszSQL, &err);
    4454         446 :     if (err != OGRERR_NONE)
    4455         417 :         mdId = -1;
    4456         446 :     sqlite3_free(pszSQL);
    4457             : 
    4458         446 :     if (bIsEmpty)
    4459             :     {
    4460         141 :         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         305 :         if (mdId >= 0)
    4475             :         {
    4476          23 :             pszSQL = sqlite3_mprintf(
    4477             :                 "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
    4478             :                 pszXML, mdId);
    4479             :         }
    4480             :         else
    4481             :         {
    4482             :             pszSQL =
    4483         282 :                 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         305 :         SQLCommand(hDB, pszSQL);
    4489         305 :         sqlite3_free(pszSQL);
    4490             : 
    4491         305 :         CPLFree(pszXML);
    4492             : 
    4493         305 :         if (mdId < 0)
    4494             :         {
    4495         282 :             const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
    4496         282 :             if (pszTableName != nullptr && pszTableName[0] != '\0')
    4497             :             {
    4498         270 :                 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         540 :                     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          23 :             pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
    4517             :                                      "timestamp = %s WHERE md_file_id = %d",
    4518          46 :                                      GetCurrentDateEscapedSQL().c_str(), mdId);
    4519             :         }
    4520         305 :         SQLCommand(hDB, pszSQL);
    4521         305 :         sqlite3_free(pszSQL);
    4522             :     }
    4523             : }
    4524             : 
    4525             : /************************************************************************/
    4526             : /*                        CreateMetadataTables()                        */
    4527             : /************************************************************************/
    4528             : 
    4529         275 : bool GDALGeoPackageDataset::CreateMetadataTables()
    4530             : {
    4531             :     const bool bCreateTriggers =
    4532         275 :         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         550 :                       ")";
    4542             : 
    4543             :     /* From D.2. metadata Table 40. metadata Trigger Definition SQL  */
    4544         275 :     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         275 :     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         275 :              ")";
    4598             : 
    4599             :     /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
    4600             :      * Definition SQL   */
    4601         275 :     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         275 :     if (bCreateTriggers)
    4695             :     {
    4696           0 :         osSQL += ";";
    4697           0 :         osSQL += pszMetadataReferenceTriggers;
    4698             :     }
    4699             : 
    4700         275 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    4701           0 :         return false;
    4702             : 
    4703         275 :     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         275 :              "'read-write')";
    4710             : 
    4711         275 :     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         275 :              "'read-write')";
    4718             : 
    4719         275 :     const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
    4720         275 :     m_nHasMetadataTables = bOK;
    4721         275 :     return bOK;
    4722             : }
    4723             : 
    4724             : /************************************************************************/
    4725             : /*                            FlushMetadata()                           */
    4726             : /************************************************************************/
    4727             : 
    4728        7983 : void GDALGeoPackageDataset::FlushMetadata()
    4729             : {
    4730        7983 :     if (!m_bMetadataDirty || m_poParentDS != nullptr ||
    4731         335 :         m_nCreateMetadataTables == FALSE)
    4732        7654 :         return;
    4733         329 :     m_bMetadataDirty = false;
    4734             : 
    4735         329 :     if (eAccess == GA_ReadOnly)
    4736             :     {
    4737           3 :         return;
    4738             :     }
    4739             : 
    4740         326 :     bool bCanWriteAreaOrPoint =
    4741         650 :         !m_bGridCellEncodingAsCO &&
    4742         324 :         (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
    4743         326 :     if (!m_osRasterTable.empty())
    4744             :     {
    4745             :         const char *pszIdentifier =
    4746         131 :             GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
    4747             :         const char *pszDescription =
    4748         131 :             GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
    4749         159 :         if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
    4750          28 :             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         138 :         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         131 :         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         326 :     char **papszMDDup = nullptr;
    4801         518 :     for (char **papszIter = GDALGeoPackageDataset::GetMetadata();
    4802         518 :          papszIter && *papszIter; ++papszIter)
    4803             :     {
    4804         192 :         if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
    4805          28 :             continue;
    4806         164 :         if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
    4807           8 :             continue;
    4808         156 :         if (STARTS_WITH_CI(*papszIter, "ZOOM_LEVEL="))
    4809          14 :             continue;
    4810         142 :         if (STARTS_WITH_CI(*papszIter, "GPKG_METADATA_ITEM_"))
    4811           4 :             continue;
    4812         138 :         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         112 :         papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4819             :     }
    4820             : 
    4821         326 :     CPLXMLNode *psXMLNode = nullptr;
    4822             :     {
    4823         326 :         GDALMultiDomainMetadata oLocalMDMD;
    4824         326 :         CSLConstList papszDomainList = oMDMD.GetDomainList();
    4825         326 :         CSLConstList papszIter = papszDomainList;
    4826         326 :         oLocalMDMD.SetMetadata(papszMDDup);
    4827         627 :         while (papszIter && *papszIter)
    4828             :         {
    4829         301 :             if (!EQUAL(*papszIter, "") &&
    4830         146 :                 !EQUAL(*papszIter, "IMAGE_STRUCTURE") &&
    4831          15 :                 !EQUAL(*papszIter, "GEOPACKAGE"))
    4832             :             {
    4833           8 :                 oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
    4834             :                                        *papszIter);
    4835             :             }
    4836         301 :             papszIter++;
    4837             :         }
    4838         326 :         if (m_nBandCountFromMetadata > 0)
    4839             :         {
    4840          66 :             oLocalMDMD.SetMetadataItem(
    4841             :                 "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
    4842             :                 "IMAGE_STRUCTURE");
    4843          66 :             if (nBands == 1)
    4844             :             {
    4845          44 :                 const auto poCT = GetRasterBand(1)->GetColorTable();
    4846          44 :                 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          66 :             if (nBands == 1)
    4865             :             {
    4866          44 :                 const char *pszTILE_FORMAT = nullptr;
    4867          44 :                 switch (m_eTF)
    4868             :                 {
    4869           0 :                     case GPKG_TF_PNG_JPEG:
    4870           0 :                         pszTILE_FORMAT = "JPEG_PNG";
    4871           0 :                         break;
    4872          38 :                     case GPKG_TF_PNG:
    4873          38 :                         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          44 :                 if (pszTILE_FORMAT)
    4889           6 :                     oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
    4890             :                                                "IMAGE_STRUCTURE");
    4891             :             }
    4892             :         }
    4893         457 :         if (GetRasterCount() > 0 &&
    4894         131 :             GetRasterBand(1)->GetRasterDataType() == GDT_Byte)
    4895             :         {
    4896         101 :             int bHasNoData = FALSE;
    4897             :             const double dfNoDataValue =
    4898         101 :                 GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    4899         101 :             if (bHasNoData)
    4900             :             {
    4901           3 :                 oLocalMDMD.SetMetadataItem("NODATA_VALUE",
    4902             :                                            CPLSPrintf("%.17g", dfNoDataValue),
    4903             :                                            "IMAGE_STRUCTURE");
    4904             :             }
    4905             :         }
    4906         562 :         for (int i = 1; i <= GetRasterCount(); ++i)
    4907             :         {
    4908             :             auto poBand =
    4909         236 :                 cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
    4910         236 :             poBand->AddImplicitStatistics(false);
    4911         236 :             char **papszMD = GetRasterBand(i)->GetMetadata();
    4912         236 :             poBand->AddImplicitStatistics(true);
    4913         236 :             if (papszMD)
    4914             :             {
    4915          12 :                 oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
    4916             :             }
    4917             :         }
    4918         326 :         psXMLNode = oLocalMDMD.Serialize();
    4919             :     }
    4920             : 
    4921         326 :     CSLDestroy(papszMDDup);
    4922         326 :     papszMDDup = nullptr;
    4923             : 
    4924         326 :     WriteMetadata(psXMLNode, m_osRasterTable.c_str());
    4925             : 
    4926         326 :     if (!m_osRasterTable.empty())
    4927             :     {
    4928             :         char **papszGeopackageMD =
    4929         131 :             GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
    4930             : 
    4931         131 :         papszMDDup = nullptr;
    4932         140 :         for (char **papszIter = papszGeopackageMD; papszIter && *papszIter;
    4933             :              ++papszIter)
    4934             :         {
    4935           9 :             papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4936             :         }
    4937             : 
    4938         262 :         GDALMultiDomainMetadata oLocalMDMD;
    4939         131 :         oLocalMDMD.SetMetadata(papszMDDup);
    4940         131 :         CSLDestroy(papszMDDup);
    4941         131 :         papszMDDup = nullptr;
    4942         131 :         psXMLNode = oLocalMDMD.Serialize();
    4943             : 
    4944         131 :         WriteMetadata(psXMLNode, nullptr);
    4945             :     }
    4946             : 
    4947         535 :     for (int i = 0; i < m_nLayers; i++)
    4948             :     {
    4949             :         const char *pszIdentifier =
    4950         209 :             m_papoLayers[i]->GetMetadataItem("IDENTIFIER");
    4951             :         const char *pszDescription =
    4952         209 :             m_papoLayers[i]->GetMetadataItem("DESCRIPTION");
    4953         209 :         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         209 :         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         209 :         papszMDDup = nullptr;
    4973         582 :         for (char **papszIter = m_papoLayers[i]->GetMetadata();
    4974         582 :              papszIter && *papszIter; ++papszIter)
    4975             :         {
    4976         373 :             if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
    4977           3 :                 continue;
    4978         370 :             if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
    4979           3 :                 continue;
    4980         367 :             if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
    4981           0 :                 continue;
    4982         367 :             papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4983             :         }
    4984             : 
    4985             :         {
    4986         209 :             GDALMultiDomainMetadata oLocalMDMD;
    4987         209 :             char **papszDomainList = m_papoLayers[i]->GetMetadataDomainList();
    4988         209 :             char **papszIter = papszDomainList;
    4989         209 :             oLocalMDMD.SetMetadata(papszMDDup);
    4990         456 :             while (papszIter && *papszIter)
    4991             :             {
    4992         247 :                 if (!EQUAL(*papszIter, ""))
    4993         102 :                     oLocalMDMD.SetMetadata(
    4994          51 :                         m_papoLayers[i]->GetMetadata(*papszIter), *papszIter);
    4995         247 :                 papszIter++;
    4996             :             }
    4997         209 :             CSLDestroy(papszDomainList);
    4998         209 :             psXMLNode = oLocalMDMD.Serialize();
    4999             :         }
    5000             : 
    5001         209 :         CSLDestroy(papszMDDup);
    5002         209 :         papszMDDup = nullptr;
    5003             : 
    5004         209 :         WriteMetadata(psXMLNode, m_papoLayers[i]->GetName());
    5005             :     }
    5006             : }
    5007             : 
    5008             : /************************************************************************/
    5009             : /*                          GetMetadataItem()                           */
    5010             : /************************************************************************/
    5011             : 
    5012        1434 : const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
    5013             :                                                    const char *pszDomain)
    5014             : {
    5015        1434 :     pszDomain = CheckMetadataDomain(pszDomain);
    5016        1434 :     return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
    5017             : }
    5018             : 
    5019             : /************************************************************************/
    5020             : /*                            SetMetadata()                             */
    5021             : /************************************************************************/
    5022             : 
    5023         121 : CPLErr GDALGeoPackageDataset::SetMetadata(char **papszMetadata,
    5024             :                                           const char *pszDomain)
    5025             : {
    5026         121 :     pszDomain = CheckMetadataDomain(pszDomain);
    5027         121 :     m_bMetadataDirty = true;
    5028         121 :     GetMetadata(); /* force loading from storage if needed */
    5029         121 :     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         806 : int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
    5051             :                                   int nYSize, int nBandsIn, GDALDataType eDT,
    5052             :                                   char **papszOptions)
    5053             : {
    5054        1612 :     CPLString osCommand;
    5055             : 
    5056             :     /* First, ensure there isn't any such file yet. */
    5057             :     VSIStatBufL sStatBuf;
    5058             : 
    5059         806 :     if (nBandsIn != 0)
    5060             :     {
    5061         208 :         if (eDT == GDT_Byte)
    5062             :         {
    5063         138 :             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         775 :     const size_t nFilenameLen = strlen(pszFilename);
    5092         775 :     const bool bGpkgZip =
    5093         770 :         (nFilenameLen > strlen(".gpkg.zip") &&
    5094        1545 :          !STARTS_WITH(pszFilename, "/vsizip/") &&
    5095         770 :          EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
    5096             : 
    5097             :     const bool bUseTempFile =
    5098         776 :         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         775 :                             "FORCED")));
    5104             : 
    5105         775 :     bool bFileExists = false;
    5106         775 :     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         775 :     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         772 :         m_pszFilename = CPLStrdup(pszFilename);
    5141             :     }
    5142         775 :     m_bNew = true;
    5143         775 :     eAccess = GA_Update;
    5144         775 :     m_bDateTimeWithTZ =
    5145         775 :         EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
    5146             :               "WITH_TZ");
    5147             : 
    5148             :     // for test/debug purposes only. true is the nominal value
    5149         775 :     m_bPNGSupports2Bands =
    5150         775 :         CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
    5151         775 :     m_bPNGSupportsCT =
    5152         775 :         CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
    5153             : 
    5154         775 :     if (!OpenOrCreateDB(bFileExists
    5155             :                             ? SQLITE_OPEN_READWRITE
    5156             :                             : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
    5157           4 :         return FALSE;
    5158             : 
    5159             :     /* Default to synchronous=off for performance for new file */
    5160        1534 :     if (!bFileExists &&
    5161         763 :         CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
    5162             :     {
    5163         317 :         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         771 :     SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
    5169             : 
    5170         771 :     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         771 :     const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
    5204         771 :     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         771 :     SoftStartTransaction();
    5234             : 
    5235        1542 :     CPLString osSQL;
    5236         771 :     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         763 :                 "description TEXT";
    5248         763 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
    5249         929 :                                              "NO")) ||
    5250         166 :             (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         763 :                  "definition, description";
    5270         763 :         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         763 :             "84 spheroid'";
    5283         763 :         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         763 :             "definition, description";
    5305         763 :         if (m_bHasDefinition12_063)
    5306          42 :             osSQL += ", definition_12_063";
    5307             :         osSQL += ") VALUES ("
    5308             :                  "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
    5309         763 :                  "'undefined Cartesian coordinate reference system'";
    5310         763 :         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         763 :             "definition, description";
    5325         763 :         if (m_bHasDefinition12_063)
    5326          42 :             osSQL += ", definition_12_063";
    5327             :         osSQL += ") VALUES ("
    5328             :                  "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
    5329         763 :                  "'undefined geographic coordinate reference system'";
    5330         763 :         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         763 :                  ")";
    5350             : 
    5351             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    5352         763 :         if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
    5353             :         {
    5354         758 :             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         758 :                      ")";
    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         763 :             CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
    5370         763 :         if (bCreateGeometryColumns)
    5371             :         {
    5372         762 :             m_bHasGPKGGeometryColumns = true;
    5373         762 :             osSQL += ";";
    5374         762 :             osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
    5375             :         }
    5376             :     }
    5377             : 
    5378             :     const bool bCreateTriggers =
    5379         771 :         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        1542 :              nullptr) == 0) ||
    5386         770 :         (!bFileExists &&
    5387         763 :          CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
    5388             :     {
    5389         763 :         if (!osSQL.empty())
    5390         762 :             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         763 :                  ")";
    5423             : 
    5424         763 :         if (bCreateTriggers)
    5425             :         {
    5426             :             /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
    5427             :              * Definition SQL */
    5428         763 :             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         763 :             osSQL += ";";
    5500         763 :             osSQL += pszTileMatrixTrigger;
    5501             :         }
    5502             :     }
    5503             : 
    5504         771 :     if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
    5505           0 :         return FALSE;
    5506             : 
    5507         771 :     if (!bFileExists)
    5508             :     {
    5509             :         const char *pszMetadataTables =
    5510         763 :             CSLFetchNameValue(papszOptions, "METADATA_TABLES");
    5511         763 :         if (pszMetadataTables)
    5512           6 :             m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
    5513             : 
    5514         763 :         if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
    5515           0 :             return FALSE;
    5516             : 
    5517         763 :         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         771 :     if (nBandsIn != 0)
    5557             :     {
    5558         174 :         const std::string osTableName = CPLGetBasenameSafe(m_pszFilename);
    5559             :         m_osRasterTable = CSLFetchNameValueDef(papszOptions, "RASTER_TABLE",
    5560         174 :                                                osTableName.c_str());
    5561         174 :         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         174 :         m_bIdentifierAsCO =
    5568         174 :             CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
    5569             :         m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
    5570         174 :                                               m_osRasterTable);
    5571         174 :         m_bDescriptionAsCO =
    5572         174 :             CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
    5573             :         m_osDescription =
    5574         174 :             CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
    5575         174 :         SetDataType(eDT);
    5576         174 :         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         174 :             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         174 :         osSQL = pszSQL;
    5592         174 :         sqlite3_free(pszSQL);
    5593             : 
    5594         174 :         if (bCreateTriggers)
    5595             :         {
    5596         174 :             osSQL += ";";
    5597         174 :             osSQL += CreateRasterTriggersSQL(m_osRasterTable);
    5598             :         }
    5599             : 
    5600         174 :         OGRErr eErr = SQLCommand(hDB, osSQL);
    5601         174 :         if (OGRERR_NONE != eErr)
    5602           0 :             return FALSE;
    5603             : 
    5604         174 :         const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
    5605         174 :         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         147 :         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         134 :             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          63 :             else if (nBandsIn == 1)
    5642          52 :                 m_eTF = GPKG_TF_PNG;
    5643             :         }
    5644             : 
    5645         174 :         if (eDT != GDT_Byte)
    5646             :         {
    5647          40 :             if (!CreateTileGriddedTable(papszOptions))
    5648           0 :                 return FALSE;
    5649             :         }
    5650             : 
    5651         174 :         nRasterXSize = nXSize;
    5652         174 :         nRasterYSize = nYSize;
    5653             : 
    5654             :         const char *pszTileSize =
    5655         174 :             CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
    5656             :         const char *pszTileWidth =
    5657         174 :             CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
    5658             :         const char *pszTileHeight =
    5659         174 :             CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
    5660         174 :         int nTileWidth = atoi(pszTileWidth);
    5661         174 :         int nTileHeight = atoi(pszTileHeight);
    5662         174 :         if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
    5663         348 :              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         481 :         for (int i = 1; i <= nBandsIn; i++)
    5673         307 :             SetBand(
    5674         307 :                 i, new GDALGeoPackageRasterBand(this, nTileWidth, nTileHeight));
    5675             : 
    5676         174 :         GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
    5677             :                                         "IMAGE_STRUCTURE");
    5678         174 :         GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
    5679         174 :         if (!m_osDescription.empty())
    5680           1 :             GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
    5681             : 
    5682         174 :         ParseCompressionOptions(papszOptions);
    5683             : 
    5684         174 :         if (m_eTF == GPKG_TF_WEBP)
    5685             :         {
    5686          10 :             if (!RegisterWebPExtension())
    5687           0 :                 return FALSE;
    5688             :         }
    5689             : 
    5690             :         m_osTilingScheme =
    5691         174 :             CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
    5692         174 :         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         152 :             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         770 :     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         770 :     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         770 :     SetApplicationAndUserVersionId();
    5765             : 
    5766             :     /* Default to synchronous=off for performance for new file */
    5767        1532 :     if (!bFileExists &&
    5768         762 :         CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
    5769             :     {
    5770         317 :         SQLCommand(hDB, "PRAGMA synchronous = OFF");
    5771             :     }
    5772             : 
    5773         770 :     return TRUE;
    5774             : }
    5775             : 
    5776             : /************************************************************************/
    5777             : /*                        RemoveOGREmptyTable()                         */
    5778             : /************************************************************************/
    5779             : 
    5780         593 : void GDALGeoPackageDataset::RemoveOGREmptyTable()
    5781             : {
    5782             :     // Run with sqlite3_exec since we don't want errors to be emitted
    5783         593 :     sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
    5784             :                  nullptr);
    5785         593 :     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         593 :     if (m_bHasGPKGOGRContents)
    5790             :     {
    5791         581 :         sqlite3_exec(hDB,
    5792             :                      "DELETE FROM gpkg_ogr_contents WHERE "
    5793             :                      "table_name = 'ogr_empty_table'",
    5794             :                      nullptr, nullptr, nullptr);
    5795             :     }
    5796             : #endif
    5797         593 :     sqlite3_exec(hDB,
    5798             :                  "DELETE FROM gpkg_geometry_columns WHERE "
    5799             :                  "table_name = 'ogr_empty_table'",
    5800             :                  nullptr, nullptr, nullptr);
    5801         593 : }
    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         148 : GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
    6001             :                                                GDALDataset *poSrcDS,
    6002             :                                                int bStrict, char **papszOptions,
    6003             :                                                GDALProgressFunc pfnProgress,
    6004             :                                                void *pProgressData)
    6005             : {
    6006         148 :     const int nBands = poSrcDS->GetRasterCount();
    6007         148 :     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         146 :         CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
    6023             : 
    6024         292 :     CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
    6025         146 :     if (CPLTestBool(
    6026         152 :             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         146 :     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         145 :     const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
    6043         290 :     if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
    6044         145 :         !EQUAL(pszUnitType, ""))
    6045             :     {
    6046           1 :         apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
    6047             :     }
    6048             : 
    6049         145 :     if (EQUAL(pszTilingScheme, "CUSTOM"))
    6050             :     {
    6051         121 :         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         121 :         GDALGeoPackageDataset *poDS = nullptr;
    6059             :         GDALDriver *poThisDriver =
    6060         121 :             GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
    6061         121 :         if (poThisDriver != nullptr)
    6062             :         {
    6063         121 :             apszUpdatedOptions.SetNameValue("SKIP_HOLES", "YES");
    6064         121 :             poDS = cpl::down_cast<GDALGeoPackageDataset *>(
    6065             :                 poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
    6066             :                                                 apszUpdatedOptions, pfnProgress,
    6067         121 :                                                 pProgressData));
    6068             : 
    6069         225 :             if (poDS != nullptr &&
    6070         121 :                 poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_Byte &&
    6071             :                 nBands <= 3)
    6072             :             {
    6073          68 :                 poDS->m_nBandCountFromMetadata = nBands;
    6074          68 :                 poDS->m_bMetadataDirty = true;
    6075             :             }
    6076             :         }
    6077         121 :         if (poDS)
    6078         104 :             poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    6079         121 :         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         440 : void GDALGeoPackageDataset::ParseCompressionOptions(char **papszOptions)
    6497             : {
    6498         440 :     const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
    6499         440 :     if (pszZLevel)
    6500           0 :         m_nZLevel = atoi(pszZLevel);
    6501             : 
    6502         440 :     const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
    6503         440 :     if (pszQuality)
    6504           0 :         m_nQuality = atoi(pszQuality);
    6505             : 
    6506         440 :     const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
    6507         440 :     if (pszDither)
    6508           0 :         m_bDither = CPLTestBool(pszDither);
    6509         440 : }
    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       13923 : OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer)
    6561             : 
    6562             : {
    6563       13923 :     if (iLayer < 0 || iLayer >= m_nLayers)
    6564           6 :         return nullptr;
    6565             :     else
    6566       13917 :         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         685 : GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
    6641             :                                     const OGRGeomFieldDefn *poSrcGeomFieldDefn,
    6642             :                                     CSLConstList papszOptions)
    6643             : {
    6644             :     /* -------------------------------------------------------------------- */
    6645             :     /*      Verify we are in update mode.                                   */
    6646             :     /* -------------------------------------------------------------------- */
    6647         685 :     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         685 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
    6659             :     const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
    6660        2055 :                                            : std::string(pszLayerName));
    6661             : 
    6662             :     const auto eGType =
    6663         685 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
    6664             :     const auto poSpatialRef =
    6665         685 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
    6666             : 
    6667         685 :     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         685 :     const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
    6678         685 :     if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
    6679           0 :         pszIdentifier = nullptr;
    6680         685 :     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         682 :         CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
    6721         682 :     if (pszGeomColumnName == nullptr) /* deprecated name */
    6722         637 :         pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
    6723         682 :     if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
    6724             :     {
    6725         585 :         pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
    6726         585 :         if (pszGeomColumnName && pszGeomColumnName[0] == 0)
    6727         581 :             pszGeomColumnName = nullptr;
    6728             :     }
    6729         682 :     if (pszGeomColumnName == nullptr)
    6730         633 :         pszGeomColumnName = "geom";
    6731             :     const bool bGeomNullable =
    6732         682 :         CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
    6733             : 
    6734             :     /* Read FID option */
    6735         682 :     const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
    6736         682 :     if (pszFIDColumnName == nullptr)
    6737         648 :         pszFIDColumnName = "fid";
    6738             : 
    6739         682 :     if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
    6740             :     {
    6741         682 :         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         682 :         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         682 :         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         881 :     for (int iLayer = 0; iLayer < m_nLayers; iLayer++)
    6774             :     {
    6775         200 :         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         681 :     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          73 :         m_papoLayers[0]->FinishOrDisableThreadedRTree();
    6801             :     }
    6802             : 
    6803             :     /* Create a blank layer. */
    6804             :     auto poLayer = std::unique_ptr<OGRGeoPackageTableLayer>(
    6805        1362 :         new OGRGeoPackageTableLayer(this, osTableName.c_str()));
    6806             : 
    6807         681 :     OGRSpatialReference *poSRS = nullptr;
    6808         681 :     if (poSpatialRef)
    6809             :     {
    6810         214 :         poSRS = poSpatialRef->Clone();
    6811         214 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6812             :     }
    6813        1363 :     poLayer->SetCreationParameters(
    6814             :         eGType,
    6815         682 :         bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
    6816             :         bGeomNullable, poSRS, CSLFetchNameValue(papszOptions, "SRID"),
    6817        1362 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
    6818             :                            : OGRGeomCoordinatePrecision(),
    6819         681 :         CPLTestBool(
    6820             :             CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
    6821         681 :         CPLTestBool(CSLFetchNameValueDef(
    6822             :             papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
    6823         682 :         bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
    6824             :         pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
    6825         681 :     if (poSRS)
    6826             :     {
    6827         214 :         poSRS->Release();
    6828             :     }
    6829             : 
    6830         681 :     poLayer->SetLaunder(bLaunder);
    6831             : 
    6832             :     /* Should we create a spatial index ? */
    6833         681 :     const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
    6834         681 :     int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
    6835         681 :     if (eGType != wkbNone && bCreateSpatialIndex)
    6836             :     {
    6837         607 :         poLayer->SetDeferredSpatialIndexCreation(true);
    6838             :     }
    6839             : 
    6840         681 :     poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
    6841         681 :     poLayer->SetTruncateFieldsFlag(
    6842         681 :         CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
    6843         681 :     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         681 :         CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
    6873         681 :     if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
    6874             :     {
    6875           2 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
    6876             :     }
    6877         679 :     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         678 :     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         677 :     else if (EQUAL(pszDateTimePrecision, "AUTO"))
    6894             :     {
    6895         676 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6896         665 :             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         680 :     m_bRemoveOGREmptyTable = true;
    6910             : 
    6911        1360 :     m_papoLayers = static_cast<OGRGeoPackageTableLayer **>(CPLRealloc(
    6912         680 :         m_papoLayers, sizeof(OGRGeoPackageTableLayer *) * (m_nLayers + 1)));
    6913         680 :     auto poRet = poLayer.release();
    6914         680 :     m_papoLayers[m_nLayers] = poRet;
    6915         680 :     m_nLayers++;
    6916         680 :     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          38 : OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
    6939             : {
    6940             :     // Temporary remove foreign key checks
    6941             :     const GPKGTemporaryForeignKeyCheckDisabler
    6942          38 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    6943             : 
    6944          38 :     char *pszSQL = sqlite3_mprintf(
    6945             :         "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
    6946             :         pszLayerName);
    6947          38 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
    6948          38 :     sqlite3_free(pszSQL);
    6949             : 
    6950          38 :     if (eErr == OGRERR_NONE && HasExtensionsTable())
    6951             :     {
    6952          36 :         pszSQL = sqlite3_mprintf(
    6953             :             "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
    6954             :             pszLayerName);
    6955          36 :         eErr = SQLCommand(hDB, pszSQL);
    6956          36 :         sqlite3_free(pszSQL);
    6957             :     }
    6958             : 
    6959          38 :     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          38 :     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          38 :     if (eErr == OGRERR_NONE)
    7042             :     {
    7043          38 :         pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
    7044          38 :         eErr = SQLCommand(hDB, pszSQL);
    7045          38 :         sqlite3_free(pszSQL);
    7046             :     }
    7047             : 
    7048             :     // Check foreign key integrity
    7049          38 :     if (eErr == OGRERR_NONE)
    7050             :     {
    7051          38 :         eErr = PragmaCheck("foreign_key_check", "", 0);
    7052             :     }
    7053             : 
    7054          76 :     return eErr;
    7055             : }
    7056             : 
    7057             : /************************************************************************/
    7058             : /*                            DeleteLayer()                             */
    7059             : /************************************************************************/
    7060             : 
    7061          35 : OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
    7062             : {
    7063          35 :     if (!GetUpdate() || iLayer < 0 || iLayer >= m_nLayers)
    7064           2 :         return OGRERR_FAILURE;
    7065             : 
    7066          33 :     m_papoLayers[iLayer]->ResetReading();
    7067          33 :     m_papoLayers[iLayer]->SyncToDisk();
    7068             : 
    7069          66 :     CPLString osLayerName = m_papoLayers[iLayer]->GetName();
    7070             : 
    7071          33 :     CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
    7072             : 
    7073             :     // Temporary remove foreign key checks
    7074             :     const GPKGTemporaryForeignKeyCheckDisabler
    7075          33 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7076             : 
    7077          33 :     OGRErr eErr = SoftStartTransaction();
    7078             : 
    7079          33 :     if (eErr == OGRERR_NONE)
    7080             :     {
    7081          33 :         if (m_papoLayers[iLayer]->HasSpatialIndex())
    7082          30 :             m_papoLayers[iLayer]->DropSpatialIndex();
    7083             : 
    7084             :         char *pszSQL =
    7085          33 :             sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
    7086             :                             "lower(table_name) = lower('%q')",
    7087             :                             osLayerName.c_str());
    7088          33 :         eErr = SQLCommand(hDB, pszSQL);
    7089          33 :         sqlite3_free(pszSQL);
    7090             :     }
    7091             : 
    7092          33 :     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          33 :     if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
    7103             :     {
    7104          33 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
    7105             :                                        "lower(table_name) = lower('%q')",
    7106             :                                        osLayerName.c_str());
    7107          33 :         eErr = SQLCommand(hDB, pszSQL);
    7108          33 :         sqlite3_free(pszSQL);
    7109             :     }
    7110             : #endif
    7111             : 
    7112          33 :     if (eErr == OGRERR_NONE)
    7113             :     {
    7114          33 :         eErr = DeleteLayerCommon(osLayerName.c_str());
    7115             :     }
    7116             : 
    7117          33 :     if (eErr == OGRERR_NONE)
    7118             :     {
    7119          33 :         eErr = SoftCommitTransaction();
    7120          33 :         if (eErr == OGRERR_NONE)
    7121             :         {
    7122             :             /* Delete the layer object and remove the gap in the layers list */
    7123          33 :             delete m_papoLayers[iLayer];
    7124          33 :             memmove(m_papoLayers + iLayer, m_papoLayers + iLayer + 1,
    7125          33 :                     sizeof(void *) * (m_nLayers - iLayer - 1));
    7126          33 :             m_nLayers--;
    7127             :         }
    7128             :     }
    7129             :     else
    7130             :     {
    7131           0 :         SoftRollbackTransaction();
    7132             :     }
    7133             : 
    7134          33 :     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         398 : int GDALGeoPackageDataset::TestCapability(const char *pszCap)
    7460             : {
    7461         398 :     if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
    7462         247 :         EQUAL(pszCap, "RenameLayer"))
    7463             :     {
    7464         151 :         return GetUpdate();
    7465             :     }
    7466         247 :     else if (EQUAL(pszCap, ODsCCurveGeometries))
    7467          12 :         return TRUE;
    7468         235 :     else if (EQUAL(pszCap, ODsCMeasuredGeometries))
    7469           8 :         return TRUE;
    7470         227 :     else if (EQUAL(pszCap, ODsCZGeometries))
    7471           8 :         return TRUE;
    7472         219 :     else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
    7473         219 :              EQUAL(pszCap, GDsCAddRelationship) ||
    7474         219 :              EQUAL(pszCap, GDsCDeleteRelationship) ||
    7475         219 :              EQUAL(pszCap, GDsCUpdateRelationship) ||
    7476         219 :              EQUAL(pszCap, ODsCAddFieldDomain))
    7477           1 :         return GetUpdate();
    7478             : 
    7479         218 :     return OGRSQLiteBaseDataSource::TestCapability(pszCap);
    7480             : }
    7481             : 
    7482             : /************************************************************************/
    7483             : /*                       ResetReadingAllLayers()                        */
    7484             : /************************************************************************/
    7485             : 
    7486          52 : void GDALGeoPackageDataset::ResetReadingAllLayers()
    7487             : {
    7488         109 :     for (int i = 0; i < m_nLayers; i++)
    7489             :     {
    7490          57 :         m_papoLayers[i]->ResetReading();
    7491             :     }
    7492          52 : }
    7493             : 
    7494             : /************************************************************************/
    7495             : /*                             ExecuteSQL()                             */
    7496             : /************************************************************************/
    7497             : 
    7498             : static const char *const apszFuncsWithSideEffects[] = {
    7499             :     "CreateSpatialIndex",
    7500             :     "DisableSpatialIndex",
    7501             :     "HasSpatialIndex",
    7502             :     "RegisterGeometryExtension",
    7503             : };
    7504             : 
    7505        5360 : OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
    7506             :                                             OGRGeometry *poSpatialFilter,
    7507             :                                             const char *pszDialect)
    7508             : 
    7509             : {
    7510        5360 :     m_bHasReadMetadataFromStorage = false;
    7511             : 
    7512        5360 :     FlushMetadata();
    7513             : 
    7514        5378 :     while (*pszSQLCommand != '\0' &&
    7515        5378 :            isspace(static_cast<unsigned char>(*pszSQLCommand)))
    7516          18 :         pszSQLCommand++;
    7517             : 
    7518       10720 :     CPLString osSQLCommand(pszSQLCommand);
    7519        5360 :     if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
    7520          48 :         osSQLCommand.pop_back();
    7521             : 
    7522        5360 :     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        5291 :             osSQLCommand.ifind("insert into ") != std::string::npos ||
    7529        2179 :             osSQLCommand.ifind("insert or replace into ") !=
    7530        7470 :                 std::string::npos ||
    7531        2133 :             osSQLCommand.ifind("delete from ") != std::string::npos;
    7532             :         const bool bRollback =
    7533        5291 :             osSQLCommand.ifind("rollback ") != std::string::npos;
    7534             : #endif
    7535             : 
    7536        6832 :         for (int i = 0; i < m_nLayers; i++)
    7537             :         {
    7538        1541 :             if (m_papoLayers[i]->SyncToDisk() != OGRERR_NONE)
    7539           0 :                 return nullptr;
    7540             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7541        1742 :             if (bRollback || (bInsertOrDelete &&
    7542         201 :                               osSQLCommand.ifind(m_papoLayers[i]->GetName()) !=
    7543             :                                   std::string::npos))
    7544             :             {
    7545         155 :                 m_papoLayers[i]->DisableFeatureCount();
    7546             :             }
    7547             : #endif
    7548             :         }
    7549             :     }
    7550             : 
    7551        5360 :     if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
    7552        5359 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
    7553        5359 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
    7554        5359 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
    7555             :     {
    7556           1 :         OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
    7557             :     }
    7558        5359 :     else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
    7559        5358 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
    7560        5358 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
    7561        5358 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
    7562             :     {
    7563           1 :         OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
    7564             :     }
    7565             : 
    7566             :     /* -------------------------------------------------------------------- */
    7567             :     /*      DEBUG "SELECT nolock" command.                                  */
    7568             :     /* -------------------------------------------------------------------- */
    7569        5429 :     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        5357 :     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        5353 :     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        5349 :     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        5345 :     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        5337 :     if (EQUAL(osSQLCommand, "VACUUM"))
    7707             :     {
    7708          12 :         ResetReadingAllLayers();
    7709             :     }
    7710             : 
    7711        5337 :     if (EQUAL(osSQLCommand, "BEGIN"))
    7712             :     {
    7713           0 :         SoftStartTransaction();
    7714           0 :         return nullptr;
    7715             :     }
    7716        5337 :     else if (EQUAL(osSQLCommand, "COMMIT"))
    7717             :     {
    7718           0 :         SoftCommitTransaction();
    7719           0 :         return nullptr;
    7720             :     }
    7721        5337 :     else if (EQUAL(osSQLCommand, "ROLLBACK"))
    7722             :     {
    7723           0 :         SoftRollbackTransaction();
    7724           0 :         return nullptr;
    7725             :     }
    7726             : 
    7727        5337 :     else if (STARTS_WITH_CI(osSQLCommand, "DELETE FROM "))
    7728             :     {
    7729             :         // Optimize truncation of a table, especially if it has a spatial
    7730             :         // index.
    7731          20 :         const CPLStringList aosTokens(SQLTokenize(osSQLCommand));
    7732          20 :         if (aosTokens.size() == 3)
    7733             :         {
    7734          14 :             const char *pszTableName = aosTokens[2];
    7735             :             OGRGeoPackageTableLayer *poLayer =
    7736           8 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7737          22 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7738          14 :             if (poLayer)
    7739             :             {
    7740           6 :                 poLayer->Truncate();
    7741           6 :                 return nullptr;
    7742             :             }
    7743             :         }
    7744             :     }
    7745             : 
    7746        5317 :     else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
    7747           1 :         return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
    7748        5316 :     else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
    7749          66 :              !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
    7750          66 :              !EQUAL(pszDialect, "DEBUG"))
    7751           0 :         return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
    7752           0 :                                        pszDialect);
    7753             : 
    7754             :     /* -------------------------------------------------------------------- */
    7755             :     /*      Prepare statement.                                              */
    7756             :     /* -------------------------------------------------------------------- */
    7757        5330 :     sqlite3_stmt *hSQLStmt = nullptr;
    7758             : 
    7759             :     /* This will speed-up layer creation */
    7760             :     /* ORDER BY are costly to evaluate and are not necessary to establish */
    7761             :     /* the layer definition. */
    7762        5330 :     bool bUseStatementForGetNextFeature = true;
    7763        5330 :     bool bEmptyLayer = false;
    7764       10660 :     CPLString osSQLCommandTruncated(osSQLCommand);
    7765             : 
    7766       17574 :     if (osSQLCommand.ifind("SELECT ") == 0 &&
    7767        6122 :         CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
    7768         758 :             std::string::npos &&
    7769         758 :         osSQLCommand.ifind(" UNION ") == std::string::npos &&
    7770        6880 :         osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
    7771         758 :         osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
    7772             :     {
    7773         758 :         size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
    7774         758 :         if (nOrderByPos != std::string::npos)
    7775             :         {
    7776           8 :             osSQLCommandTruncated.resize(nOrderByPos);
    7777           8 :             bUseStatementForGetNextFeature = false;
    7778             :         }
    7779             :     }
    7780             : 
    7781        5330 :     int rc = prepareSql(hDB, osSQLCommandTruncated.c_str(),
    7782        5330 :                         static_cast<int>(osSQLCommandTruncated.size()),
    7783             :                         &hSQLStmt, nullptr);
    7784             : 
    7785        5330 :     if (rc != SQLITE_OK)
    7786             :     {
    7787           9 :         CPLError(CE_Failure, CPLE_AppDefined,
    7788             :                  "In ExecuteSQL(): sqlite3_prepare_v2(%s):\n  %s",
    7789             :                  osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
    7790             : 
    7791           9 :         if (hSQLStmt != nullptr)
    7792             :         {
    7793           0 :             sqlite3_finalize(hSQLStmt);
    7794             :         }
    7795             : 
    7796           9 :         return nullptr;
    7797             :     }
    7798             : 
    7799             :     /* -------------------------------------------------------------------- */
    7800             :     /*      Do we get a resultset?                                          */
    7801             :     /* -------------------------------------------------------------------- */
    7802        5321 :     rc = sqlite3_step(hSQLStmt);
    7803             : 
    7804        6884 :     for (int i = 0; i < m_nLayers; i++)
    7805             :     {
    7806        1563 :         m_papoLayers[i]->RunDeferredDropRTreeTableIfNecessary();
    7807             :     }
    7808             : 
    7809        5321 :     if (rc != SQLITE_ROW)
    7810             :     {
    7811        4609 :         if (rc != SQLITE_DONE)
    7812             :         {
    7813           7 :             CPLError(CE_Failure, CPLE_AppDefined,
    7814             :                      "In ExecuteSQL(): sqlite3_step(%s):\n  %s",
    7815             :                      osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
    7816             : 
    7817           7 :             sqlite3_finalize(hSQLStmt);
    7818           7 :             return nullptr;
    7819             :         }
    7820             : 
    7821        4602 :         if (EQUAL(osSQLCommand, "VACUUM"))
    7822             :         {
    7823          12 :             sqlite3_finalize(hSQLStmt);
    7824             :             /* VACUUM rewrites the DB, so we need to reset the application id */
    7825          12 :             SetApplicationAndUserVersionId();
    7826          12 :             return nullptr;
    7827             :         }
    7828             : 
    7829        4590 :         if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
    7830             :         {
    7831        4466 :             sqlite3_finalize(hSQLStmt);
    7832        4466 :             return nullptr;
    7833             :         }
    7834             : 
    7835         124 :         bUseStatementForGetNextFeature = false;
    7836         124 :         bEmptyLayer = true;
    7837             :     }
    7838             : 
    7839             :     /* -------------------------------------------------------------------- */
    7840             :     /*      Special case for some functions which must be run               */
    7841             :     /*      only once                                                       */
    7842             :     /* -------------------------------------------------------------------- */
    7843         836 :     if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
    7844             :     {
    7845        3804 :         for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
    7846             :                                          sizeof(apszFuncsWithSideEffects[0]);
    7847             :              i++)
    7848             :         {
    7849        3069 :             if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
    7850             :                        strlen(apszFuncsWithSideEffects[i])))
    7851             :             {
    7852         112 :                 if (sqlite3_column_count(hSQLStmt) == 1 &&
    7853          56 :                     sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
    7854             :                 {
    7855          56 :                     int ret = sqlite3_column_int(hSQLStmt, 0);
    7856             : 
    7857          56 :                     sqlite3_finalize(hSQLStmt);
    7858             : 
    7859             :                     return new OGRSQLiteSingleFeatureLayer(
    7860          56 :                         apszFuncsWithSideEffects[i], ret);
    7861             :                 }
    7862             :             }
    7863             :         }
    7864             :     }
    7865          45 :     else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
    7866             :     {
    7867          63 :         if (sqlite3_column_count(hSQLStmt) == 1 &&
    7868          18 :             sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
    7869             :         {
    7870          15 :             int ret = sqlite3_column_int(hSQLStmt, 0);
    7871             : 
    7872          15 :             sqlite3_finalize(hSQLStmt);
    7873             : 
    7874          15 :             return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
    7875          15 :                                                    ret);
    7876             :         }
    7877          33 :         else if (sqlite3_column_count(hSQLStmt) == 1 &&
    7878           3 :                  sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
    7879             :         {
    7880             :             const char *pszRet = reinterpret_cast<const char *>(
    7881           3 :                 sqlite3_column_text(hSQLStmt, 0));
    7882             : 
    7883             :             OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
    7884           3 :                 osSQLCommand.c_str() + 7, pszRet);
    7885             : 
    7886           3 :             sqlite3_finalize(hSQLStmt);
    7887             : 
    7888           3 :             return poRet;
    7889             :         }
    7890             :     }
    7891             : 
    7892             :     /* -------------------------------------------------------------------- */
    7893             :     /*      Create layer.                                                   */
    7894             :     /* -------------------------------------------------------------------- */
    7895             : 
    7896             :     OGRLayer *poLayer = new OGRGeoPackageSelectLayer(
    7897             :         this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
    7898         762 :         bEmptyLayer);
    7899             : 
    7900         765 :     if (poSpatialFilter != nullptr &&
    7901           3 :         poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
    7902           3 :         poLayer->SetSpatialFilter(0, poSpatialFilter);
    7903             : 
    7904         762 :     return poLayer;
    7905             : }
    7906             : 
    7907             : /************************************************************************/
    7908             : /*                          ReleaseResultSet()                          */
    7909             : /************************************************************************/
    7910             : 
    7911         792 : void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
    7912             : 
    7913             : {
    7914         792 :     delete poLayer;
    7915         792 : }
    7916             : 
    7917             : /************************************************************************/
    7918             : /*                         HasExtensionsTable()                         */
    7919             : /************************************************************************/
    7920             : 
    7921        5870 : bool GDALGeoPackageDataset::HasExtensionsTable()
    7922             : {
    7923        5870 :     return SQLGetInteger(
    7924             :                hDB,
    7925             :                "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
    7926             :                "AND type IN ('table', 'view')",
    7927        5870 :                nullptr) == 1;
    7928             : }
    7929             : 
    7930             : /************************************************************************/
    7931             : /*                    CheckUnknownExtensions()                          */
    7932             : /************************************************************************/
    7933             : 
    7934        1383 : void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
    7935             : {
    7936        1383 :     if (!HasExtensionsTable())
    7937         193 :         return;
    7938             : 
    7939        1190 :     char *pszSQL = nullptr;
    7940        1190 :     if (!bCheckRasterTable)
    7941         984 :         pszSQL = sqlite3_mprintf(
    7942             :             "SELECT extension_name, definition, scope FROM gpkg_extensions "
    7943             :             "WHERE (table_name IS NULL "
    7944             :             "AND extension_name IS NOT NULL "
    7945             :             "AND definition IS NOT NULL "
    7946             :             "AND scope IS NOT NULL "
    7947             :             "AND extension_name NOT IN ("
    7948             :             "'gdal_aspatial', "
    7949             :             "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
    7950             :             "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
    7951             :                                        // 17-066r1 finalization
    7952             :             "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
    7953             :             "'gpkg_metadata', "
    7954             :             "'gpkg_schema', "
    7955             :             "'gpkg_crs_wkt', "
    7956             :             "'gpkg_crs_wkt_1_1', "
    7957             :             "'related_tables', 'gpkg_related_tables')) "
    7958             : #ifdef WORKAROUND_SQLITE3_BUGS
    7959             :             "OR 0 "
    7960             : #endif
    7961             :             "LIMIT 1000");
    7962             :     else
    7963         206 :         pszSQL = sqlite3_mprintf(
    7964             :             "SELECT extension_name, definition, scope FROM gpkg_extensions "
    7965             :             "WHERE (lower(table_name) = lower('%q') "
    7966             :             "AND extension_name IS NOT NULL "
    7967             :             "AND definition IS NOT NULL "
    7968             :             "AND scope IS NOT NULL "
    7969             :             "AND extension_name NOT IN ("
    7970             :             "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
    7971             :             "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
    7972             :                                        // 17-066r1 finalization
    7973             :             "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
    7974             :             "'gpkg_metadata', "
    7975             :             "'gpkg_schema', "
    7976             :             "'gpkg_crs_wkt', "
    7977             :             "'gpkg_crs_wkt_1_1', "
    7978             :             "'related_tables', 'gpkg_related_tables')) "
    7979             : #ifdef WORKAROUND_SQLITE3_BUGS
    7980             :             "OR 0 "
    7981             : #endif
    7982             :             "LIMIT 1000",
    7983             :             m_osRasterTable.c_str());
    7984             : 
    7985        2380 :     auto oResultTable = SQLQuery(GetDB(), pszSQL);
    7986        1190 :     sqlite3_free(pszSQL);
    7987        1190 :     if (oResultTable && oResultTable->RowCount() > 0)
    7988             :     {
    7989          44 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    7990             :         {
    7991          22 :             const char *pszExtName = oResultTable->GetValue(0, i);
    7992          22 :             const char *pszDefinition = oResultTable->GetValue(1, i);
    7993          22 :             const char *pszScope = oResultTable->GetValue(2, i);
    7994          22 :             if (pszExtName == nullptr || pszDefinition == nullptr ||
    7995             :                 pszScope == nullptr)
    7996             :             {
    7997           0 :                 continue;
    7998             :             }
    7999             : 
    8000          22 :             if (EQUAL(pszExtName, "gpkg_webp"))
    8001             :             {
    8002          16 :                 if (GDALGetDriverByName("WEBP") == nullptr)
    8003             :                 {
    8004           1 :                     CPLError(
    8005             :                         CE_Warning, CPLE_AppDefined,
    8006             :                         "Table %s contains WEBP tiles, but GDAL configured "
    8007             :                         "without WEBP support. Data will be missing",
    8008             :                         m_osRasterTable.c_str());
    8009             :                 }
    8010          16 :                 m_eTF = GPKG_TF_WEBP;
    8011          16 :                 continue;
    8012             :             }
    8013           6 :             if (EQUAL(pszExtName, "gpkg_zoom_other"))
    8014             :             {
    8015           2 :                 m_bZoomOther = true;
    8016           2 :                 continue;
    8017             :             }
    8018             : 
    8019           4 :             if (GetUpdate() && EQUAL(pszScope, "write-only"))
    8020             :             {
    8021           1 :                 CPLError(
    8022             :                     CE_Warning, CPLE_AppDefined,
    8023             :                     "Database relies on the '%s' (%s) extension that should "
    8024             :                     "be implemented for safe write-support, but is not "
    8025             :                     "currently. "
    8026             :                     "Update of that database are strongly discouraged to avoid "
    8027             :                     "corruption.",
    8028             :                     pszExtName, pszDefinition);
    8029             :             }
    8030           3 :             else if (GetUpdate() && EQUAL(pszScope, "read-write"))
    8031             :             {
    8032           1 :                 CPLError(
    8033             :                     CE_Warning, CPLE_AppDefined,
    8034             :                     "Database relies on the '%s' (%s) extension that should "
    8035             :                     "be implemented in order to read/write it safely, but is "
    8036             :                     "not currently. "
    8037             :                     "Some data may be missing while reading that database, and "
    8038             :                     "updates are strongly discouraged.",
    8039             :                     pszExtName, pszDefinition);
    8040             :             }
    8041           2 :             else if (EQUAL(pszScope, "read-write") &&
    8042             :                      // None of the NGA extensions at
    8043             :                      // http://ngageoint.github.io/GeoPackage/docs/extensions/
    8044             :                      // affect read-only scenarios
    8045           1 :                      !STARTS_WITH(pszExtName, "nga_"))
    8046             :             {
    8047           1 :                 CPLError(
    8048             :                     CE_Warning, CPLE_AppDefined,
    8049             :                     "Database relies on the '%s' (%s) extension that should "
    8050             :                     "be implemented in order to read it safely, but is not "
    8051             :                     "currently. "
    8052             :                     "Some data may be missing while reading that database.",
    8053             :                     pszExtName, pszDefinition);
    8054             :             }
    8055             :         }
    8056             :     }
    8057             : }
    8058             : 
    8059             : /************************************************************************/
    8060             : /*                         HasGDALAspatialExtension()                       */
    8061             : /************************************************************************/
    8062             : 
    8063         931 : bool GDALGeoPackageDataset::HasGDALAspatialExtension()
    8064             : {
    8065         931 :     if (!HasExtensionsTable())
    8066          86 :         return false;
    8067             : 
    8068             :     auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
    8069             :                                       "WHERE (extension_name = 'gdal_aspatial' "
    8070             :                                       "AND table_name IS NULL "
    8071             :                                       "AND column_name IS NULL)"
    8072             : #ifdef WORKAROUND_SQLITE3_BUGS
    8073             :                                       " OR 0"
    8074             : #endif
    8075         845 :     );
    8076         845 :     bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
    8077         845 :     return bHasExtension;
    8078             : }
    8079             : 
    8080             : std::string
    8081         176 : GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName)
    8082             : {
    8083             :     char *pszSQL;
    8084         176 :     std::string osSQL;
    8085             :     /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
    8086             :      * Definition SQL  */
    8087         176 :     pszSQL = sqlite3_mprintf(
    8088             :         "CREATE TRIGGER \"%w_zoom_insert\" "
    8089             :         "BEFORE INSERT ON \"%w\" "
    8090             :         "FOR EACH ROW BEGIN "
    8091             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8092             :         "constraint: zoom_level not specified for table in "
    8093             :         "gpkg_tile_matrix') "
    8094             :         "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
    8095             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
    8096             :         "END; "
    8097             :         "CREATE TRIGGER \"%w_zoom_update\" "
    8098             :         "BEFORE UPDATE OF zoom_level ON \"%w\" "
    8099             :         "FOR EACH ROW BEGIN "
    8100             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8101             :         "constraint: zoom_level not specified for table in "
    8102             :         "gpkg_tile_matrix') "
    8103             :         "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
    8104             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
    8105             :         "END; "
    8106             :         "CREATE TRIGGER \"%w_tile_column_insert\" "
    8107             :         "BEFORE INSERT ON \"%w\" "
    8108             :         "FOR EACH ROW BEGIN "
    8109             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8110             :         "constraint: tile_column cannot be < 0') "
    8111             :         "WHERE (NEW.tile_column < 0) ; "
    8112             :         "SELECT RAISE(ABORT, 'insert 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_column_update\" "
    8120             :         "BEFORE UPDATE OF tile_column ON \"%w\" "
    8121             :         "FOR EACH ROW BEGIN "
    8122             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8123             :         "constraint: tile_column cannot be < 0') "
    8124             :         "WHERE (NEW.tile_column < 0) ; "
    8125             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8126             :         "constraint: tile_column must by < matrix_width specified for "
    8127             :         "table and zoom level in gpkg_tile_matrix') "
    8128             :         "WHERE NOT (NEW.tile_column < (SELECT matrix_width 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_insert\" "
    8133             :         "BEFORE INSERT ON \"%w\" "
    8134             :         "FOR EACH ROW BEGIN "
    8135             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8136             :         "constraint: tile_row cannot be < 0') "
    8137             :         "WHERE (NEW.tile_row < 0) ; "
    8138             :         "SELECT RAISE(ABORT, 'insert 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             :         "CREATE TRIGGER \"%w_tile_row_update\" "
    8146             :         "BEFORE UPDATE OF tile_row ON \"%w\" "
    8147             :         "FOR EACH ROW BEGIN "
    8148             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8149             :         "constraint: tile_row cannot be < 0') "
    8150             :         "WHERE (NEW.tile_row < 0) ; "
    8151             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8152             :         "constraint: tile_row must by < matrix_height specified for "
    8153             :         "table and zoom level in gpkg_tile_matrix') "
    8154             :         "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
    8155             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8156             :         "zoom_level = NEW.zoom_level)); "
    8157             :         "END; ",
    8158             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8159             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8160             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8161             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8162             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8163             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8164             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8165             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8166             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8167             :         osTableName.c_str());
    8168         176 :     osSQL = pszSQL;
    8169         176 :     sqlite3_free(pszSQL);
    8170         176 :     return osSQL;
    8171             : }
    8172             : 
    8173             : /************************************************************************/
    8174             : /*                  CreateExtensionsTableIfNecessary()                  */
    8175             : /************************************************************************/
    8176             : 
    8177        1063 : OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
    8178             : {
    8179             :     /* Check if the table gpkg_extensions exists */
    8180        1063 :     if (HasExtensionsTable())
    8181         391 :         return OGRERR_NONE;
    8182             : 
    8183             :     /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
    8184             :     /* in a corresponding row in the gpkg_extensions table. The absence of a */
    8185             :     /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
    8186             :     /* SHALL both indicate the absence of extensions to a GeoPackage. */
    8187         672 :     const char *pszCreateGpkgExtensions =
    8188             :         "CREATE TABLE gpkg_extensions ("
    8189             :         "table_name TEXT,"
    8190             :         "column_name TEXT,"
    8191             :         "extension_name TEXT NOT NULL,"
    8192             :         "definition TEXT NOT NULL,"
    8193             :         "scope TEXT NOT NULL,"
    8194             :         "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
    8195             :         ")";
    8196             : 
    8197         672 :     return SQLCommand(hDB, pszCreateGpkgExtensions);
    8198             : }
    8199             : 
    8200             : /************************************************************************/
    8201             : /*                    OGR_GPKG_Intersects_Spatial_Filter()              */
    8202             : /************************************************************************/
    8203             : 
    8204       23135 : void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
    8205             :                                         sqlite3_value **argv)
    8206             : {
    8207       23135 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8208             :     {
    8209           0 :         sqlite3_result_int(pContext, 0);
    8210       23125 :         return;
    8211             :     }
    8212             : 
    8213             :     auto poLayer =
    8214       23135 :         static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
    8215             : 
    8216       23135 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8217             :     const GByte *pabyBLOB =
    8218       23135 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8219             : 
    8220             :     GPkgHeader sHeader;
    8221       46270 :     if (poLayer->m_bFilterIsEnvelope &&
    8222       23135 :         OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
    8223             :     {
    8224       23135 :         if (sHeader.bExtentHasXY)
    8225             :         {
    8226          95 :             OGREnvelope sEnvelope;
    8227          95 :             sEnvelope.MinX = sHeader.MinX;
    8228          95 :             sEnvelope.MinY = sHeader.MinY;
    8229          95 :             sEnvelope.MaxX = sHeader.MaxX;
    8230          95 :             sEnvelope.MaxY = sHeader.MaxY;
    8231          95 :             if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
    8232             :             {
    8233          31 :                 sqlite3_result_int(pContext, 1);
    8234          31 :                 return;
    8235             :             }
    8236             :         }
    8237             : 
    8238             :         // Check if at least one point falls into the layer filter envelope
    8239             :         // nHeaderLen is > 0 for GeoPackage geometries
    8240       46208 :         if (sHeader.nHeaderLen > 0 &&
    8241       23104 :             OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
    8242       23104 :                                         nBLOBLen - sHeader.nHeaderLen,
    8243       23104 :                                         poLayer->m_sFilterEnvelope))
    8244             :         {
    8245       23094 :             sqlite3_result_int(pContext, 1);
    8246       23094 :             return;
    8247             :         }
    8248             :     }
    8249             : 
    8250             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8251          10 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8252          10 :     if (poGeom == nullptr)
    8253             :     {
    8254             :         // Try also spatialite geometry blobs
    8255           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8256           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8257           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8258             :         {
    8259           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8260           0 :             sqlite3_result_int(pContext, 0);
    8261           0 :             return;
    8262             :         }
    8263           0 :         poGeom.reset(poGeomSpatialite);
    8264             :     }
    8265             : 
    8266          10 :     sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
    8267             : }
    8268             : 
    8269             : /************************************************************************/
    8270             : /*                      OGRGeoPackageSTMinX()                           */
    8271             : /************************************************************************/
    8272             : 
    8273      242587 : static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
    8274             :                                 sqlite3_value **argv)
    8275             : {
    8276             :     GPkgHeader sHeader;
    8277      242587 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8278             :     {
    8279           3 :         sqlite3_result_null(pContext);
    8280           3 :         return;
    8281             :     }
    8282      242584 :     sqlite3_result_double(pContext, sHeader.MinX);
    8283             : }
    8284             : 
    8285             : /************************************************************************/
    8286             : /*                      OGRGeoPackageSTMinY()                           */
    8287             : /************************************************************************/
    8288             : 
    8289      242585 : static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
    8290             :                                 sqlite3_value **argv)
    8291             : {
    8292             :     GPkgHeader sHeader;
    8293      242585 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8294             :     {
    8295           1 :         sqlite3_result_null(pContext);
    8296           1 :         return;
    8297             :     }
    8298      242584 :     sqlite3_result_double(pContext, sHeader.MinY);
    8299             : }
    8300             : 
    8301             : /************************************************************************/
    8302             : /*                      OGRGeoPackageSTMaxX()                           */
    8303             : /************************************************************************/
    8304             : 
    8305      242585 : static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
    8306             :                                 sqlite3_value **argv)
    8307             : {
    8308             :     GPkgHeader sHeader;
    8309      242585 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8310             :     {
    8311           1 :         sqlite3_result_null(pContext);
    8312           1 :         return;
    8313             :     }
    8314      242584 :     sqlite3_result_double(pContext, sHeader.MaxX);
    8315             : }
    8316             : 
    8317             : /************************************************************************/
    8318             : /*                      OGRGeoPackageSTMaxY()                           */
    8319             : /************************************************************************/
    8320             : 
    8321      242585 : static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
    8322             :                                 sqlite3_value **argv)
    8323             : {
    8324             :     GPkgHeader sHeader;
    8325      242585 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8326             :     {
    8327           1 :         sqlite3_result_null(pContext);
    8328           1 :         return;
    8329             :     }
    8330      242584 :     sqlite3_result_double(pContext, sHeader.MaxY);
    8331             : }
    8332             : 
    8333             : /************************************************************************/
    8334             : /*                     OGRGeoPackageSTIsEmpty()                         */
    8335             : /************************************************************************/
    8336             : 
    8337      243919 : static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
    8338             :                                    sqlite3_value **argv)
    8339             : {
    8340             :     GPkgHeader sHeader;
    8341      243919 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8342             :     {
    8343           2 :         sqlite3_result_null(pContext);
    8344           2 :         return;
    8345             :     }
    8346      243917 :     sqlite3_result_int(pContext, sHeader.bEmpty);
    8347             : }
    8348             : 
    8349             : /************************************************************************/
    8350             : /*                    OGRGeoPackageSTGeometryType()                     */
    8351             : /************************************************************************/
    8352             : 
    8353           7 : static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
    8354             :                                         sqlite3_value **argv)
    8355             : {
    8356             :     GPkgHeader sHeader;
    8357             : 
    8358           7 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8359             :     const GByte *pabyBLOB =
    8360           7 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8361             :     OGRwkbGeometryType eGeometryType;
    8362             : 
    8363          13 :     if (nBLOBLen < 8 ||
    8364           6 :         GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
    8365             :     {
    8366           2 :         if (OGRSQLiteGetSpatialiteGeometryHeader(
    8367             :                 pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
    8368           2 :                 nullptr, nullptr, nullptr) == OGRERR_NONE)
    8369             :         {
    8370           1 :             sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
    8371             :                                 SQLITE_TRANSIENT);
    8372           4 :             return;
    8373             :         }
    8374             :         else
    8375             :         {
    8376           1 :             sqlite3_result_null(pContext);
    8377           1 :             return;
    8378             :         }
    8379             :     }
    8380             : 
    8381           5 :     if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
    8382             :     {
    8383           2 :         sqlite3_result_null(pContext);
    8384           2 :         return;
    8385             :     }
    8386             : 
    8387           3 :     OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
    8388             :                                         wkbVariantIso, &eGeometryType);
    8389           3 :     if (err != OGRERR_NONE)
    8390           1 :         sqlite3_result_null(pContext);
    8391             :     else
    8392           2 :         sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
    8393             :                             SQLITE_TRANSIENT);
    8394             : }
    8395             : 
    8396             : /************************************************************************/
    8397             : /*                 OGRGeoPackageSTEnvelopesIntersects()                 */
    8398             : /************************************************************************/
    8399             : 
    8400         118 : static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
    8401             :                                                int argc, sqlite3_value **argv)
    8402             : {
    8403             :     GPkgHeader sHeader;
    8404         118 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8405             :     {
    8406           2 :         sqlite3_result_int(pContext, FALSE);
    8407         107 :         return;
    8408             :     }
    8409         116 :     const double dfMinX = sqlite3_value_double(argv[1]);
    8410         116 :     if (sHeader.MaxX < dfMinX)
    8411             :     {
    8412          93 :         sqlite3_result_int(pContext, FALSE);
    8413          93 :         return;
    8414             :     }
    8415          23 :     const double dfMinY = sqlite3_value_double(argv[2]);
    8416          23 :     if (sHeader.MaxY < dfMinY)
    8417             :     {
    8418          11 :         sqlite3_result_int(pContext, FALSE);
    8419          11 :         return;
    8420             :     }
    8421          12 :     const double dfMaxX = sqlite3_value_double(argv[3]);
    8422          12 :     if (sHeader.MinX > dfMaxX)
    8423             :     {
    8424           1 :         sqlite3_result_int(pContext, FALSE);
    8425           1 :         return;
    8426             :     }
    8427          11 :     const double dfMaxY = sqlite3_value_double(argv[4]);
    8428          11 :     sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
    8429             : }
    8430             : 
    8431             : /************************************************************************/
    8432             : /*              OGRGeoPackageSTEnvelopesIntersectsTwoParams()           */
    8433             : /************************************************************************/
    8434             : 
    8435             : static void
    8436           3 : OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
    8437             :                                             sqlite3_value **argv)
    8438             : {
    8439             :     GPkgHeader sHeader;
    8440           3 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
    8441             :     {
    8442           0 :         sqlite3_result_int(pContext, FALSE);
    8443           2 :         return;
    8444             :     }
    8445             :     GPkgHeader sHeader2;
    8446           3 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
    8447             :                                 1))
    8448             :     {
    8449           0 :         sqlite3_result_int(pContext, FALSE);
    8450           0 :         return;
    8451             :     }
    8452           3 :     if (sHeader.MaxX < sHeader2.MinX)
    8453             :     {
    8454           1 :         sqlite3_result_int(pContext, FALSE);
    8455           1 :         return;
    8456             :     }
    8457           2 :     if (sHeader.MaxY < sHeader2.MinY)
    8458             :     {
    8459           0 :         sqlite3_result_int(pContext, FALSE);
    8460           0 :         return;
    8461             :     }
    8462           2 :     if (sHeader.MinX > sHeader2.MaxX)
    8463             :     {
    8464           1 :         sqlite3_result_int(pContext, FALSE);
    8465           1 :         return;
    8466             :     }
    8467           1 :     sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
    8468             : }
    8469             : 
    8470             : /************************************************************************/
    8471             : /*                    OGRGeoPackageGPKGIsAssignable()                   */
    8472             : /************************************************************************/
    8473             : 
    8474           8 : static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
    8475             :                                           int /*argc*/, sqlite3_value **argv)
    8476             : {
    8477          15 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8478           7 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    8479             :     {
    8480           2 :         sqlite3_result_int(pContext, 0);
    8481           2 :         return;
    8482             :     }
    8483             : 
    8484             :     const char *pszExpected =
    8485           6 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8486             :     const char *pszActual =
    8487           6 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8488           6 :     int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
    8489             :                                             OGRFromOGCGeomType(pszExpected));
    8490           6 :     sqlite3_result_int(pContext, bIsAssignable);
    8491             : }
    8492             : 
    8493             : /************************************************************************/
    8494             : /*                     OGRGeoPackageSTSRID()                            */
    8495             : /************************************************************************/
    8496             : 
    8497          12 : static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
    8498             :                                 sqlite3_value **argv)
    8499             : {
    8500             :     GPkgHeader sHeader;
    8501          12 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8502             :     {
    8503           2 :         sqlite3_result_null(pContext);
    8504           2 :         return;
    8505             :     }
    8506          10 :     sqlite3_result_int(pContext, sHeader.iSrsId);
    8507             : }
    8508             : 
    8509             : /************************************************************************/
    8510             : /*                     OGRGeoPackageSetSRID()                           */
    8511             : /************************************************************************/
    8512             : 
    8513          28 : static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
    8514             :                                  sqlite3_value **argv)
    8515             : {
    8516          28 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8517             :     {
    8518           1 :         sqlite3_result_null(pContext);
    8519           1 :         return;
    8520             :     }
    8521          27 :     const int nDestSRID = sqlite3_value_int(argv[1]);
    8522             :     GPkgHeader sHeader;
    8523          27 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8524             :     const GByte *pabyBLOB =
    8525          27 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8526             : 
    8527          54 :     if (nBLOBLen < 8 ||
    8528          27 :         GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
    8529             :     {
    8530             :         // Try also spatialite geometry blobs
    8531           0 :         OGRGeometry *poGeom = nullptr;
    8532           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
    8533             :             OGRERR_NONE)
    8534             :         {
    8535           0 :             sqlite3_result_null(pContext);
    8536           0 :             return;
    8537             :         }
    8538           0 :         size_t nBLOBDestLen = 0;
    8539             :         GByte *pabyDestBLOB =
    8540           0 :             GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
    8541           0 :         if (!pabyDestBLOB)
    8542             :         {
    8543           0 :             sqlite3_result_null(pContext);
    8544           0 :             return;
    8545             :         }
    8546           0 :         sqlite3_result_blob(pContext, pabyDestBLOB,
    8547             :                             static_cast<int>(nBLOBDestLen), VSIFree);
    8548           0 :         return;
    8549             :     }
    8550             : 
    8551          27 :     GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
    8552          27 :     memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
    8553          27 :     int32_t nSRIDToSerialize = nDestSRID;
    8554          27 :     if (OGR_SWAP(sHeader.eByteOrder))
    8555           0 :         nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
    8556          27 :     memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
    8557          27 :     sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
    8558             : }
    8559             : 
    8560             : /************************************************************************/
    8561             : /*                   OGRGeoPackageSTMakeValid()                         */
    8562             : /************************************************************************/
    8563             : 
    8564           3 : static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
    8565             :                                      sqlite3_value **argv)
    8566             : {
    8567           3 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8568             :     {
    8569           2 :         sqlite3_result_null(pContext);
    8570           2 :         return;
    8571             :     }
    8572           1 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8573             :     const GByte *pabyBLOB =
    8574           1 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8575             : 
    8576             :     GPkgHeader sHeader;
    8577           1 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8578             :     {
    8579           0 :         sqlite3_result_null(pContext);
    8580           0 :         return;
    8581             :     }
    8582             : 
    8583             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8584           1 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8585           1 :     if (poGeom == nullptr)
    8586             :     {
    8587             :         // Try also spatialite geometry blobs
    8588           0 :         OGRGeometry *poGeomPtr = nullptr;
    8589           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
    8590             :             OGRERR_NONE)
    8591             :         {
    8592           0 :             sqlite3_result_null(pContext);
    8593           0 :             return;
    8594             :         }
    8595           0 :         poGeom.reset(poGeomPtr);
    8596             :     }
    8597           1 :     auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
    8598           1 :     if (poValid == nullptr)
    8599             :     {
    8600           0 :         sqlite3_result_null(pContext);
    8601           0 :         return;
    8602             :     }
    8603             : 
    8604           1 :     size_t nBLOBDestLen = 0;
    8605           1 :     GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
    8606             :                                               nullptr, &nBLOBDestLen);
    8607           1 :     if (!pabyDestBLOB)
    8608             :     {
    8609           0 :         sqlite3_result_null(pContext);
    8610           0 :         return;
    8611             :     }
    8612           1 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    8613             :                         VSIFree);
    8614             : }
    8615             : 
    8616             : /************************************************************************/
    8617             : /*                   OGRGeoPackageSTArea()                              */
    8618             : /************************************************************************/
    8619             : 
    8620          19 : static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
    8621             :                                 sqlite3_value **argv)
    8622             : {
    8623          19 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8624             :     {
    8625           1 :         sqlite3_result_null(pContext);
    8626          15 :         return;
    8627             :     }
    8628          18 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8629             :     const GByte *pabyBLOB =
    8630          18 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8631             : 
    8632             :     GPkgHeader sHeader;
    8633           0 :     std::unique_ptr<OGRGeometry> poGeom;
    8634          18 :     if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
    8635             :     {
    8636          16 :         if (sHeader.bEmpty)
    8637             :         {
    8638           3 :             sqlite3_result_double(pContext, 0);
    8639          13 :             return;
    8640             :         }
    8641          13 :         const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
    8642          13 :         size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
    8643             :         bool bNeedSwap;
    8644             :         uint32_t nType;
    8645          13 :         if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
    8646             :         {
    8647          13 :             if (nType == wkbPolygon || nType == wkbPolygon25D ||
    8648          11 :                 nType == wkbPolygon + 1000 ||  // wkbPolygonZ
    8649          10 :                 nType == wkbPolygonM || nType == wkbPolygonZM)
    8650             :             {
    8651             :                 double dfArea;
    8652           5 :                 if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
    8653             :                 {
    8654           5 :                     sqlite3_result_double(pContext, dfArea);
    8655           5 :                     return;
    8656           0 :                 }
    8657             :             }
    8658           8 :             else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
    8659           6 :                      nType == wkbMultiPolygon + 1000 ||  // wkbMultiPolygonZ
    8660           5 :                      nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
    8661             :             {
    8662             :                 double dfArea;
    8663           5 :                 if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
    8664             :                 {
    8665           5 :                     sqlite3_result_double(pContext, dfArea);
    8666           5 :                     return;
    8667             :                 }
    8668             :             }
    8669             :         }
    8670             : 
    8671             :         // For curve geometries, fallback to OGRGeometry methods
    8672           3 :         poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8673             :     }
    8674             :     else
    8675             :     {
    8676             :         // Try also spatialite geometry blobs
    8677           2 :         OGRGeometry *poGeomPtr = nullptr;
    8678           2 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
    8679             :             OGRERR_NONE)
    8680             :         {
    8681           1 :             sqlite3_result_null(pContext);
    8682           1 :             return;
    8683             :         }
    8684           1 :         poGeom.reset(poGeomPtr);
    8685             :     }
    8686           4 :     auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
    8687           4 :     if (poSurface == nullptr)
    8688             :     {
    8689           2 :         auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
    8690           2 :         if (poMultiSurface == nullptr)
    8691             :         {
    8692           1 :             sqlite3_result_double(pContext, 0);
    8693             :         }
    8694             :         else
    8695             :         {
    8696           1 :             sqlite3_result_double(pContext, poMultiSurface->get_Area());
    8697             :         }
    8698             :     }
    8699             :     else
    8700             :     {
    8701           2 :         sqlite3_result_double(pContext, poSurface->get_Area());
    8702             :     }
    8703             : }
    8704             : 
    8705             : /************************************************************************/
    8706             : /*                     OGRGeoPackageGeodesicArea()                      */
    8707             : /************************************************************************/
    8708             : 
    8709           5 : static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
    8710             :                                       sqlite3_value **argv)
    8711             : {
    8712           5 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8713             :     {
    8714           1 :         sqlite3_result_null(pContext);
    8715           3 :         return;
    8716             :     }
    8717           4 :     if (sqlite3_value_int(argv[1]) != 1)
    8718             :     {
    8719           2 :         CPLError(CE_Warning, CPLE_NotSupported,
    8720             :                  "ST_Area(geom, use_ellipsoid) is only supported for "
    8721             :                  "use_ellipsoid = 1");
    8722             :     }
    8723             : 
    8724           4 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8725             :     const GByte *pabyBLOB =
    8726           4 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8727             :     GPkgHeader sHeader;
    8728           4 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8729             :     {
    8730           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8731           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8732           1 :         return;
    8733             :     }
    8734             : 
    8735             :     GDALGeoPackageDataset *poDS =
    8736           3 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8737             : 
    8738             :     std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS(
    8739           3 :         poDS->GetSpatialRef(sHeader.iSrsId, true));
    8740           3 :     if (poSrcSRS == nullptr)
    8741             :     {
    8742           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    8743             :                  "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8744           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8745           1 :         return;
    8746             :     }
    8747             : 
    8748             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8749           2 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8750           2 :     if (poGeom == nullptr)
    8751             :     {
    8752             :         // Try also spatialite geometry blobs
    8753           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8754           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8755           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8756             :         {
    8757           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8758           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8759           0 :             return;
    8760             :         }
    8761           0 :         poGeom.reset(poGeomSpatialite);
    8762             :     }
    8763             : 
    8764           2 :     poGeom->assignSpatialReference(poSrcSRS.get());
    8765           2 :     sqlite3_result_double(
    8766             :         pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
    8767             : }
    8768             : 
    8769             : /************************************************************************/
    8770             : /*                   OGRGeoPackageLengthOrGeodesicLength()              */
    8771             : /************************************************************************/
    8772             : 
    8773           8 : static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext,
    8774             :                                                 int argc, sqlite3_value **argv)
    8775             : {
    8776           8 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8777             :     {
    8778           2 :         sqlite3_result_null(pContext);
    8779           5 :         return;
    8780             :     }
    8781           6 :     if (argc == 2 && sqlite3_value_int(argv[1]) != 1)
    8782             :     {
    8783           2 :         CPLError(CE_Warning, CPLE_NotSupported,
    8784             :                  "ST_Length(geom, use_ellipsoid) is only supported for "
    8785             :                  "use_ellipsoid = 1");
    8786             :     }
    8787             : 
    8788           6 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8789             :     const GByte *pabyBLOB =
    8790           6 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8791             :     GPkgHeader sHeader;
    8792           6 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8793             :     {
    8794           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8795           2 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8796           2 :         return;
    8797             :     }
    8798             : 
    8799             :     GDALGeoPackageDataset *poDS =
    8800           4 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8801             : 
    8802           0 :     std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS;
    8803           4 :     if (argc == 2)
    8804             :     {
    8805           3 :         poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
    8806           3 :         if (!poSrcSRS)
    8807             :         {
    8808           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    8809             :                      "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8810           1 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8811           1 :             return;
    8812             :         }
    8813             :     }
    8814             : 
    8815             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8816           3 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8817           3 :     if (poGeom == nullptr)
    8818             :     {
    8819             :         // Try also spatialite geometry blobs
    8820           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8821           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8822           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8823             :         {
    8824           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8825           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8826           0 :             return;
    8827             :         }
    8828           0 :         poGeom.reset(poGeomSpatialite);
    8829             :     }
    8830             : 
    8831           3 :     if (argc == 2)
    8832           2 :         poGeom->assignSpatialReference(poSrcSRS.get());
    8833             : 
    8834           6 :     sqlite3_result_double(
    8835             :         pContext,
    8836           1 :         argc == 1 ? OGR_G_Length(OGRGeometry::ToHandle(poGeom.get()))
    8837           2 :                   : OGR_G_GeodesicLength(OGRGeometry::ToHandle(poGeom.get())));
    8838             : }
    8839             : 
    8840             : /************************************************************************/
    8841             : /*                      OGRGeoPackageTransform()                        */
    8842             : /************************************************************************/
    8843             : 
    8844             : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
    8845             :                             sqlite3_value **argv);
    8846             : 
    8847          32 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
    8848             :                             sqlite3_value **argv)
    8849             : {
    8850          63 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
    8851          31 :         sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
    8852             :     {
    8853           2 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8854          32 :         return;
    8855             :     }
    8856             : 
    8857          30 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8858             :     const GByte *pabyBLOB =
    8859          30 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8860             :     GPkgHeader sHeader;
    8861          30 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8862             :     {
    8863           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8864           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8865           1 :         return;
    8866             :     }
    8867             : 
    8868          29 :     const int nDestSRID = sqlite3_value_int(argv[1]);
    8869          29 :     if (sHeader.iSrsId == nDestSRID)
    8870             :     {
    8871             :         // Return blob unmodified
    8872           3 :         sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
    8873           3 :         return;
    8874             :     }
    8875             : 
    8876             :     GDALGeoPackageDataset *poDS =
    8877          26 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8878             : 
    8879             :     // Try to get the cached coordinate transformation
    8880             :     OGRCoordinateTransformation *poCT;
    8881          26 :     if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
    8882          20 :         poDS->m_nLastCachedCTDstSRId == nDestSRID)
    8883             :     {
    8884          20 :         poCT = poDS->m_poLastCachedCT.get();
    8885             :     }
    8886             :     else
    8887             :     {
    8888             :         std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
    8889           6 :             poSrcSRS(poDS->GetSpatialRef(sHeader.iSrsId, true));
    8890           6 :         if (poSrcSRS == nullptr)
    8891             :         {
    8892           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    8893             :                      "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8894           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8895           0 :             return;
    8896             :         }
    8897             : 
    8898             :         std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
    8899           6 :             poDstSRS(poDS->GetSpatialRef(nDestSRID, true));
    8900           6 :         if (poDstSRS == nullptr)
    8901             :         {
    8902           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
    8903             :                      nDestSRID);
    8904           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8905           0 :             return;
    8906             :         }
    8907             :         poCT =
    8908           6 :             OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get());
    8909           6 :         if (poCT == nullptr)
    8910             :         {
    8911           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8912           0 :             return;
    8913             :         }
    8914             : 
    8915             :         // Cache coordinate transformation for potential later reuse
    8916           6 :         poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
    8917           6 :         poDS->m_nLastCachedCTDstSRId = nDestSRID;
    8918           6 :         poDS->m_poLastCachedCT.reset(poCT);
    8919           6 :         poCT = poDS->m_poLastCachedCT.get();
    8920             :     }
    8921             : 
    8922          26 :     if (sHeader.nHeaderLen >= 8)
    8923             :     {
    8924          26 :         std::vector<GByte> &abyNewBLOB = poDS->m_abyWKBTransformCache;
    8925          26 :         abyNewBLOB.resize(nBLOBLen);
    8926          26 :         memcpy(abyNewBLOB.data(), pabyBLOB, nBLOBLen);
    8927             : 
    8928          26 :         OGREnvelope3D oEnv3d;
    8929          26 :         if (!OGRWKBTransform(abyNewBLOB.data() + sHeader.nHeaderLen,
    8930          26 :                              nBLOBLen - sHeader.nHeaderLen, poCT,
    8931          78 :                              poDS->m_oWKBTransformCache, oEnv3d) ||
    8932          26 :             !GPkgUpdateHeader(abyNewBLOB.data(), nBLOBLen, nDestSRID,
    8933             :                               oEnv3d.MinX, oEnv3d.MaxX, oEnv3d.MinY,
    8934             :                               oEnv3d.MaxY, oEnv3d.MinZ, oEnv3d.MaxZ))
    8935             :         {
    8936           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8937           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8938           0 :             return;
    8939             :         }
    8940             : 
    8941          26 :         sqlite3_result_blob(pContext, abyNewBLOB.data(), nBLOBLen,
    8942             :                             SQLITE_TRANSIENT);
    8943          26 :         return;
    8944             :     }
    8945             : 
    8946             :     // Try also spatialite geometry blobs
    8947           0 :     OGRGeometry *poGeomSpatialite = nullptr;
    8948           0 :     if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8949           0 :                                           &poGeomSpatialite) != OGRERR_NONE)
    8950             :     {
    8951           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8952           0 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8953           0 :         return;
    8954             :     }
    8955           0 :     auto poGeom = std::unique_ptr<OGRGeometry>(poGeomSpatialite);
    8956             : 
    8957           0 :     if (poGeom->transform(poCT) != OGRERR_NONE)
    8958             :     {
    8959           0 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8960           0 :         return;
    8961             :     }
    8962             : 
    8963           0 :     size_t nBLOBDestLen = 0;
    8964             :     GByte *pabyDestBLOB =
    8965           0 :         GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
    8966           0 :     if (!pabyDestBLOB)
    8967             :     {
    8968           0 :         sqlite3_result_null(pContext);
    8969           0 :         return;
    8970             :     }
    8971           0 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    8972             :                         VSIFree);
    8973             : }
    8974             : 
    8975             : /************************************************************************/
    8976             : /*                      OGRGeoPackageSridFromAuthCRS()                  */
    8977             : /************************************************************************/
    8978             : 
    8979           4 : static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
    8980             :                                          int /*argc*/, sqlite3_value **argv)
    8981             : {
    8982           7 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8983           3 :         sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
    8984             :     {
    8985           2 :         sqlite3_result_int(pContext, -1);
    8986           2 :         return;
    8987             :     }
    8988             : 
    8989             :     GDALGeoPackageDataset *poDS =
    8990           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8991             : 
    8992           2 :     char *pszSQL = sqlite3_mprintf(
    8993             :         "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
    8994             :         "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
    8995           2 :         sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
    8996           2 :     OGRErr err = OGRERR_NONE;
    8997           2 :     int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
    8998           2 :     sqlite3_free(pszSQL);
    8999           2 :     if (err != OGRERR_NONE)
    9000           1 :         nSRSId = -1;
    9001           2 :     sqlite3_result_int(pContext, nSRSId);
    9002             : }
    9003             : 
    9004             : /************************************************************************/
    9005             : /*                    OGRGeoPackageImportFromEPSG()                     */
    9006             : /************************************************************************/
    9007             : 
    9008           4 : static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
    9009             :                                         sqlite3_value **argv)
    9010             : {
    9011           4 :     if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
    9012             :     {
    9013           1 :         sqlite3_result_int(pContext, -1);
    9014           2 :         return;
    9015             :     }
    9016             : 
    9017             :     GDALGeoPackageDataset *poDS =
    9018           3 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9019           3 :     OGRSpatialReference oSRS;
    9020           3 :     if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
    9021             :     {
    9022           1 :         sqlite3_result_int(pContext, -1);
    9023           1 :         return;
    9024             :     }
    9025             : 
    9026           2 :     sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
    9027             : }
    9028             : 
    9029             : /************************************************************************/
    9030             : /*               OGRGeoPackageRegisterGeometryExtension()               */
    9031             : /************************************************************************/
    9032             : 
    9033           1 : static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
    9034             :                                                    int /*argc*/,
    9035             :                                                    sqlite3_value **argv)
    9036             : {
    9037           1 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9038           2 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
    9039           1 :         sqlite3_value_type(argv[2]) != SQLITE_TEXT)
    9040             :     {
    9041           0 :         sqlite3_result_int(pContext, 0);
    9042           0 :         return;
    9043             :     }
    9044             : 
    9045             :     const char *pszTableName =
    9046           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9047             :     const char *pszGeomName =
    9048           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9049             :     const char *pszGeomType =
    9050           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
    9051             : 
    9052             :     GDALGeoPackageDataset *poDS =
    9053           1 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9054             : 
    9055           1 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9056           1 :         poDS->GetLayerByName(pszTableName));
    9057           1 :     if (poLyr == nullptr)
    9058             :     {
    9059           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9060           0 :         sqlite3_result_int(pContext, 0);
    9061           0 :         return;
    9062             :     }
    9063           1 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9064             :     {
    9065           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9066           0 :         sqlite3_result_int(pContext, 0);
    9067           0 :         return;
    9068             :     }
    9069           1 :     const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
    9070           1 :     if (eGeomType == wkbUnknown)
    9071             :     {
    9072           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
    9073           0 :         sqlite3_result_int(pContext, 0);
    9074           0 :         return;
    9075             :     }
    9076             : 
    9077           1 :     sqlite3_result_int(
    9078             :         pContext,
    9079           1 :         static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
    9080             : }
    9081             : 
    9082             : /************************************************************************/
    9083             : /*                  OGRGeoPackageCreateSpatialIndex()                   */
    9084             : /************************************************************************/
    9085             : 
    9086          14 : static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
    9087             :                                             int /*argc*/, sqlite3_value **argv)
    9088             : {
    9089          27 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9090          13 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9091             :     {
    9092           2 :         sqlite3_result_int(pContext, 0);
    9093           2 :         return;
    9094             :     }
    9095             : 
    9096             :     const char *pszTableName =
    9097          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9098             :     const char *pszGeomName =
    9099          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9100             :     GDALGeoPackageDataset *poDS =
    9101          12 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9102             : 
    9103          12 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9104          12 :         poDS->GetLayerByName(pszTableName));
    9105          12 :     if (poLyr == nullptr)
    9106             :     {
    9107           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9108           1 :         sqlite3_result_int(pContext, 0);
    9109           1 :         return;
    9110             :     }
    9111          11 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9112             :     {
    9113           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9114           1 :         sqlite3_result_int(pContext, 0);
    9115           1 :         return;
    9116             :     }
    9117             : 
    9118          10 :     sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
    9119             : }
    9120             : 
    9121             : /************************************************************************/
    9122             : /*                  OGRGeoPackageDisableSpatialIndex()                  */
    9123             : /************************************************************************/
    9124             : 
    9125          12 : static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
    9126             :                                              int /*argc*/, sqlite3_value **argv)
    9127             : {
    9128          23 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9129          11 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9130             :     {
    9131           2 :         sqlite3_result_int(pContext, 0);
    9132           2 :         return;
    9133             :     }
    9134             : 
    9135             :     const char *pszTableName =
    9136          10 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9137             :     const char *pszGeomName =
    9138          10 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9139             :     GDALGeoPackageDataset *poDS =
    9140          10 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9141             : 
    9142          10 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9143          10 :         poDS->GetLayerByName(pszTableName));
    9144          10 :     if (poLyr == nullptr)
    9145             :     {
    9146           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9147           1 :         sqlite3_result_int(pContext, 0);
    9148           1 :         return;
    9149             :     }
    9150           9 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9151             :     {
    9152           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9153           1 :         sqlite3_result_int(pContext, 0);
    9154           1 :         return;
    9155             :     }
    9156             : 
    9157           8 :     sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
    9158             : }
    9159             : 
    9160             : /************************************************************************/
    9161             : /*                  OGRGeoPackageHasSpatialIndex()                      */
    9162             : /************************************************************************/
    9163             : 
    9164          29 : static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
    9165             :                                          int /*argc*/, sqlite3_value **argv)
    9166             : {
    9167          57 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9168          28 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9169             :     {
    9170           2 :         sqlite3_result_int(pContext, 0);
    9171           2 :         return;
    9172             :     }
    9173             : 
    9174             :     const char *pszTableName =
    9175          27 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9176             :     const char *pszGeomName =
    9177          27 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9178             :     GDALGeoPackageDataset *poDS =
    9179          27 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9180             : 
    9181          27 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9182          27 :         poDS->GetLayerByName(pszTableName));
    9183          27 :     if (poLyr == nullptr)
    9184             :     {
    9185           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9186           1 :         sqlite3_result_int(pContext, 0);
    9187           1 :         return;
    9188             :     }
    9189          26 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9190             :     {
    9191           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9192           1 :         sqlite3_result_int(pContext, 0);
    9193           1 :         return;
    9194             :     }
    9195             : 
    9196          25 :     poLyr->RunDeferredCreationIfNecessary();
    9197          25 :     poLyr->CreateSpatialIndexIfNecessary();
    9198             : 
    9199          25 :     sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
    9200             : }
    9201             : 
    9202             : /************************************************************************/
    9203             : /*                       GPKG_hstore_get_value()                        */
    9204             : /************************************************************************/
    9205             : 
    9206           4 : static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
    9207             :                                   sqlite3_value **argv)
    9208             : {
    9209           7 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9210           3 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9211             :     {
    9212           2 :         sqlite3_result_null(pContext);
    9213           2 :         return;
    9214             :     }
    9215             : 
    9216             :     const char *pszHStore =
    9217           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9218             :     const char *pszSearchedKey =
    9219           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9220           2 :     char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
    9221           2 :     if (pszValue != nullptr)
    9222           1 :         sqlite3_result_text(pContext, pszValue, -1, CPLFree);
    9223             :     else
    9224           1 :         sqlite3_result_null(pContext);
    9225             : }
    9226             : 
    9227             : /************************************************************************/
    9228             : /*                      GPKG_GDAL_GetMemFileFromBlob()                  */
    9229             : /************************************************************************/
    9230             : 
    9231         105 : static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
    9232             : {
    9233         105 :     int nBytes = sqlite3_value_bytes(argv[0]);
    9234             :     const GByte *pabyBLOB =
    9235         105 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    9236             :     const CPLString osMemFileName(
    9237         105 :         VSIMemGenerateHiddenFilename("GPKG_GDAL_GetMemFileFromBlob"));
    9238         105 :     VSILFILE *fp = VSIFileFromMemBuffer(
    9239             :         osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
    9240         105 :     VSIFCloseL(fp);
    9241         105 :     return osMemFileName;
    9242             : }
    9243             : 
    9244             : /************************************************************************/
    9245             : /*                       GPKG_GDAL_GetMimeType()                        */
    9246             : /************************************************************************/
    9247             : 
    9248          35 : static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
    9249             :                                   sqlite3_value **argv)
    9250             : {
    9251          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9252             :     {
    9253           0 :         sqlite3_result_null(pContext);
    9254           0 :         return;
    9255             :     }
    9256             : 
    9257          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9258             :     GDALDriver *poDriver =
    9259          35 :         GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
    9260          35 :     if (poDriver != nullptr)
    9261             :     {
    9262          35 :         const char *pszRes = nullptr;
    9263          35 :         if (EQUAL(poDriver->GetDescription(), "PNG"))
    9264          23 :             pszRes = "image/png";
    9265          12 :         else if (EQUAL(poDriver->GetDescription(), "JPEG"))
    9266           6 :             pszRes = "image/jpeg";
    9267           6 :         else if (EQUAL(poDriver->GetDescription(), "WEBP"))
    9268           6 :             pszRes = "image/x-webp";
    9269           0 :         else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
    9270           0 :             pszRes = "image/tiff";
    9271             :         else
    9272           0 :             pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
    9273          35 :         sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
    9274             :     }
    9275             :     else
    9276           0 :         sqlite3_result_null(pContext);
    9277          35 :     VSIUnlink(osMemFileName);
    9278             : }
    9279             : 
    9280             : /************************************************************************/
    9281             : /*                       GPKG_GDAL_GetBandCount()                       */
    9282             : /************************************************************************/
    9283             : 
    9284          35 : static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
    9285             :                                    sqlite3_value **argv)
    9286             : {
    9287          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9288             :     {
    9289           0 :         sqlite3_result_null(pContext);
    9290           0 :         return;
    9291             :     }
    9292             : 
    9293          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9294             :     auto poDS = std::unique_ptr<GDALDataset>(
    9295             :         GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    9296          70 :                           nullptr, nullptr, nullptr));
    9297          35 :     if (poDS != nullptr)
    9298             :     {
    9299          35 :         sqlite3_result_int(pContext, poDS->GetRasterCount());
    9300             :     }
    9301             :     else
    9302           0 :         sqlite3_result_null(pContext);
    9303          35 :     VSIUnlink(osMemFileName);
    9304             : }
    9305             : 
    9306             : /************************************************************************/
    9307             : /*                       GPKG_GDAL_HasColorTable()                      */
    9308             : /************************************************************************/
    9309             : 
    9310          35 : static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
    9311             :                                     sqlite3_value **argv)
    9312             : {
    9313          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9314             :     {
    9315           0 :         sqlite3_result_null(pContext);
    9316           0 :         return;
    9317             :     }
    9318             : 
    9319          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9320             :     auto poDS = std::unique_ptr<GDALDataset>(
    9321             :         GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    9322          70 :                           nullptr, nullptr, nullptr));
    9323          35 :     if (poDS != nullptr)
    9324             :     {
    9325          35 :         sqlite3_result_int(
    9326          46 :             pContext, poDS->GetRasterCount() == 1 &&
    9327          11 :                           poDS->GetRasterBand(1)->GetColorTable() != nullptr);
    9328             :     }
    9329             :     else
    9330           0 :         sqlite3_result_null(pContext);
    9331          35 :     VSIUnlink(osMemFileName);
    9332             : }
    9333             : 
    9334             : /************************************************************************/
    9335             : /*                      GetRasterLayerDataset()                         */
    9336             : /************************************************************************/
    9337             : 
    9338             : GDALDataset *
    9339          12 : GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
    9340             : {
    9341          12 :     const auto oIter = m_oCachedRasterDS.find(pszLayerName);
    9342          12 :     if (oIter != m_oCachedRasterDS.end())
    9343          10 :         return oIter->second.get();
    9344             : 
    9345             :     auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
    9346           4 :         (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
    9347           4 :         GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
    9348           2 :     if (!poDS)
    9349             :     {
    9350           0 :         return nullptr;
    9351             :     }
    9352           2 :     m_oCachedRasterDS[pszLayerName] = std::move(poDS);
    9353           2 :     return m_oCachedRasterDS[pszLayerName].get();
    9354             : }
    9355             : 
    9356             : /************************************************************************/
    9357             : /*                   GPKG_gdal_get_layer_pixel_value()                  */
    9358             : /************************************************************************/
    9359             : 
    9360             : // NOTE: keep in sync implementations in ogrsqlitesqlfunctionscommon.cpp
    9361             : // and ogrgeopackagedatasource.cpp
    9362          13 : static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext, int argc,
    9363             :                                             sqlite3_value **argv)
    9364             : {
    9365          13 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
    9366             :     {
    9367           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    9368             :                  "Invalid arguments to gdal_get_layer_pixel_value()");
    9369           1 :         sqlite3_result_null(pContext);
    9370           1 :         return;
    9371             :     }
    9372             : 
    9373             :     const char *pszLayerName =
    9374          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9375             : 
    9376             :     GDALGeoPackageDataset *poGlobalDS =
    9377          12 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9378          12 :     auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
    9379          12 :     if (!poDS)
    9380             :     {
    9381           0 :         sqlite3_result_null(pContext);
    9382           0 :         return;
    9383             :     }
    9384             : 
    9385          12 :     OGRSQLite_gdal_get_pixel_value_common("gdal_get_layer_pixel_value",
    9386             :                                           pContext, argc, argv, poDS);
    9387             : }
    9388             : 
    9389             : /************************************************************************/
    9390             : /*                       GPKG_ogr_layer_Extent()                        */
    9391             : /************************************************************************/
    9392             : 
    9393           3 : static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
    9394             :                                   sqlite3_value **argv)
    9395             : {
    9396           3 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
    9397             :     {
    9398           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
    9399             :                  "ogr_layer_Extent");
    9400           1 :         sqlite3_result_null(pContext);
    9401           2 :         return;
    9402             :     }
    9403             : 
    9404             :     const char *pszLayerName =
    9405           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9406             :     GDALGeoPackageDataset *poDS =
    9407           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9408           2 :     OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
    9409           2 :     if (!poLayer)
    9410             :     {
    9411           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
    9412             :                  "ogr_layer_Extent");
    9413           1 :         sqlite3_result_null(pContext);
    9414           1 :         return;
    9415             :     }
    9416             : 
    9417           1 :     if (poLayer->GetGeomType() == wkbNone)
    9418             :     {
    9419           0 :         sqlite3_result_null(pContext);
    9420           0 :         return;
    9421             :     }
    9422             : 
    9423           1 :     OGREnvelope sExtent;
    9424           1 :     if (poLayer->GetExtent(&sExtent) != OGRERR_NONE)
    9425             :     {
    9426           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
    9427             :                  "ogr_layer_Extent");
    9428           0 :         sqlite3_result_null(pContext);
    9429           0 :         return;
    9430             :     }
    9431             : 
    9432           1 :     OGRPolygon oPoly;
    9433           1 :     OGRLinearRing *poRing = new OGRLinearRing();
    9434           1 :     oPoly.addRingDirectly(poRing);
    9435           1 :     poRing->addPoint(sExtent.MinX, sExtent.MinY);
    9436           1 :     poRing->addPoint(sExtent.MaxX, sExtent.MinY);
    9437           1 :     poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
    9438           1 :     poRing->addPoint(sExtent.MinX, sExtent.MaxY);
    9439           1 :     poRing->addPoint(sExtent.MinX, sExtent.MinY);
    9440             : 
    9441           1 :     const auto poSRS = poLayer->GetSpatialRef();
    9442           1 :     const int nSRID = poDS->GetSrsId(poSRS);
    9443           1 :     size_t nBLOBDestLen = 0;
    9444             :     GByte *pabyDestBLOB =
    9445           1 :         GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
    9446           1 :     if (!pabyDestBLOB)
    9447             :     {
    9448           0 :         sqlite3_result_null(pContext);
    9449           0 :         return;
    9450             :     }
    9451           1 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    9452             :                         VSIFree);
    9453             : }
    9454             : 
    9455             : /************************************************************************/
    9456             : /*                      InstallSQLFunctions()                           */
    9457             : /************************************************************************/
    9458             : 
    9459             : #ifndef SQLITE_DETERMINISTIC
    9460             : #define SQLITE_DETERMINISTIC 0
    9461             : #endif
    9462             : 
    9463             : #ifndef SQLITE_INNOCUOUS
    9464             : #define SQLITE_INNOCUOUS 0
    9465             : #endif
    9466             : 
    9467             : #ifndef UTF8_INNOCUOUS
    9468             : #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
    9469             : #endif
    9470             : 
    9471        1899 : void GDALGeoPackageDataset::InstallSQLFunctions()
    9472             : {
    9473        1899 :     InitSpatialite();
    9474             : 
    9475             :     // Enable SpatiaLite 4.3 "amphibious" mode, i.e. that SpatiaLite functions
    9476             :     // that take geometries will accept GPKG encoded geometries without
    9477             :     // explicit conversion.
    9478             :     // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
    9479             :     // error.
    9480        1899 :     sqlite3_exec(hDB, "SELECT EnableGpkgAmphibiousMode()", nullptr, nullptr,
    9481             :                  nullptr);
    9482             : 
    9483             :     /* Used by RTree Spatial Index Extension */
    9484        1899 :     sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
    9485             :                             OGRGeoPackageSTMinX, nullptr, nullptr);
    9486        1899 :     sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
    9487             :                             OGRGeoPackageSTMinY, nullptr, nullptr);
    9488        1899 :     sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
    9489             :                             OGRGeoPackageSTMaxX, nullptr, nullptr);
    9490        1899 :     sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
    9491             :                             OGRGeoPackageSTMaxY, nullptr, nullptr);
    9492        1899 :     sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
    9493             :                             OGRGeoPackageSTIsEmpty, nullptr, nullptr);
    9494             : 
    9495             :     /* Used by Geometry Type Triggers Extension */
    9496        1899 :     sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
    9497             :                             OGRGeoPackageSTGeometryType, nullptr, nullptr);
    9498        1899 :     sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
    9499             :                             nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
    9500             :                             nullptr);
    9501             : 
    9502             :     /* Used by Geometry SRS ID Triggers Extension */
    9503        1899 :     sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
    9504             :                             OGRGeoPackageSTSRID, nullptr, nullptr);
    9505             : 
    9506             :     /* Spatialite-like functions */
    9507        1899 :     sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
    9508             :                             OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
    9509        1899 :     sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
    9510             :                             OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
    9511        1899 :     sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
    9512             :                             OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
    9513             : 
    9514             :     // HSTORE functions
    9515        1899 :     sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
    9516             :                             GPKG_hstore_get_value, nullptr, nullptr);
    9517             : 
    9518             :     // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
    9519        1899 :     sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
    9520             :                             OGRGeoPackageTransform, nullptr, nullptr);
    9521        1899 :     sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
    9522             :                             OGRGeoPackageTransform, nullptr, nullptr);
    9523        1899 :     sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
    9524             :                             OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
    9525             : 
    9526        1899 :     sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
    9527             :                             OGRGeoPackageSTEnvelopesIntersectsTwoParams,
    9528             :                             nullptr, nullptr);
    9529        1899 :     sqlite3_create_function(
    9530             :         hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
    9531             :         OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
    9532             : 
    9533        1899 :     sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
    9534             :                             OGRGeoPackageSTEnvelopesIntersects, nullptr,
    9535             :                             nullptr);
    9536        1899 :     sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
    9537             :                             nullptr, OGRGeoPackageSTEnvelopesIntersects,
    9538             :                             nullptr, nullptr);
    9539             : 
    9540             :     // Implementation that directly hacks the GeoPackage geometry blob header
    9541        1899 :     sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
    9542             :                             OGRGeoPackageSetSRID, nullptr, nullptr);
    9543             : 
    9544             :     // GDAL specific function
    9545        1899 :     sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
    9546             :                             OGRGeoPackageImportFromEPSG, nullptr, nullptr);
    9547             : 
    9548             :     // May be used by ogrmerge.py
    9549        1899 :     sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
    9550             :                             this, OGRGeoPackageRegisterGeometryExtension,
    9551             :                             nullptr, nullptr);
    9552             : 
    9553        1899 :     if (OGRGeometryFactory::haveGEOS())
    9554             :     {
    9555        1899 :         sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
    9556             :                                 OGRGeoPackageSTMakeValid, nullptr, nullptr);
    9557             :     }
    9558             : 
    9559        1899 :     sqlite3_create_function(hDB, "ST_Length", 1, UTF8_INNOCUOUS, nullptr,
    9560             :                             OGRGeoPackageLengthOrGeodesicLength, nullptr,
    9561             :                             nullptr);
    9562        1899 :     sqlite3_create_function(hDB, "ST_Length", 2, UTF8_INNOCUOUS, this,
    9563             :                             OGRGeoPackageLengthOrGeodesicLength, nullptr,
    9564             :                             nullptr);
    9565             : 
    9566        1899 :     sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
    9567             :                             OGRGeoPackageSTArea, nullptr, nullptr);
    9568        1899 :     sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
    9569             :                             OGRGeoPackageGeodesicArea, nullptr, nullptr);
    9570             : 
    9571             :     // Debug functions
    9572        1899 :     if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
    9573             :     {
    9574         417 :         sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
    9575             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9576             :                                 GPKG_GDAL_GetMimeType, nullptr, nullptr);
    9577         417 :         sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
    9578             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9579             :                                 GPKG_GDAL_GetBandCount, nullptr, nullptr);
    9580         417 :         sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
    9581             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9582             :                                 GPKG_GDAL_HasColorTable, nullptr, nullptr);
    9583             :     }
    9584             : 
    9585        1899 :     sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
    9586             :                             this, GPKG_gdal_get_layer_pixel_value, nullptr,
    9587             :                             nullptr);
    9588        1899 :     sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 6, SQLITE_UTF8,
    9589             :                             this, GPKG_gdal_get_layer_pixel_value, nullptr,
    9590             :                             nullptr);
    9591             : 
    9592             :     // Function from VirtualOGR
    9593        1899 :     sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
    9594             :                             GPKG_ogr_layer_Extent, nullptr, nullptr);
    9595             : 
    9596        1899 :     m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
    9597        1899 : }
    9598             : 
    9599             : /************************************************************************/
    9600             : /*                         OpenOrCreateDB()                             */
    9601             : /************************************************************************/
    9602             : 
    9603        1900 : bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
    9604             : {
    9605        1900 :     const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
    9606             :         flags, /*bRegisterOGR2SQLiteExtensions=*/false,
    9607             :         /*bLoadExtensions=*/true);
    9608        1900 :     if (!bSuccess)
    9609           6 :         return false;
    9610             : 
    9611             :     // Turning on recursive_triggers is needed so that DELETE triggers fire
    9612             :     // in a INSERT OR REPLACE statement. In particular this is needed to
    9613             :     // make sure gpkg_ogr_contents.feature_count is properly updated.
    9614        1894 :     SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
    9615             : 
    9616        1894 :     InstallSQLFunctions();
    9617             : 
    9618             :     const char *pszSqlitePragma =
    9619        1894 :         CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
    9620        1894 :     OGRErr eErr = OGRERR_NONE;
    9621           6 :     if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
    9622             :         // Older sqlite versions don't have this pragma
    9623        3794 :         SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
    9624        1894 :         eErr == OGRERR_NONE)
    9625             :     {
    9626        1894 :         bool bNeedsTrustedSchema = false;
    9627             : 
    9628             :         // Current SQLite versions require PRAGMA trusted_schema = 1 to be
    9629             :         // able to use the RTree from triggers, which is only needed when
    9630             :         // modifying the RTree.
    9631        4683 :         if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
    9632        2893 :              (flags & SQLITE_OPEN_CREATE) != 0) &&
    9633         999 :             OGRSQLiteRTreeRequiresTrustedSchemaOn())
    9634             :         {
    9635         999 :             bNeedsTrustedSchema = true;
    9636             :         }
    9637             : 
    9638             : #ifdef HAVE_SPATIALITE
    9639             :         // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
    9640         895 :         if (!bNeedsTrustedSchema && HasExtensionsTable() &&
    9641         817 :             SQLGetInteger(
    9642             :                 hDB,
    9643             :                 "SELECT 1 FROM gpkg_extensions WHERE "
    9644             :                 "extension_name ='gdal_spatialite_computed_geom_column'",
    9645           1 :                 nullptr) == 1 &&
    9646        2789 :             SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
    9647             :         {
    9648           1 :             bNeedsTrustedSchema = true;
    9649             :         }
    9650             : #endif
    9651             : 
    9652        1894 :         if (bNeedsTrustedSchema)
    9653             :         {
    9654        1000 :             CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
    9655        1000 :             SQLCommand(hDB, "PRAGMA trusted_schema = 1");
    9656             :         }
    9657             :     }
    9658             : 
    9659        1894 :     return true;
    9660             : }
    9661             : 
    9662             : /************************************************************************/
    9663             : /*                   GetLayerWithGetSpatialWhereByName()                */
    9664             : /************************************************************************/
    9665             : 
    9666             : std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
    9667          90 : GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
    9668             : {
    9669             :     OGRGeoPackageLayer *poRet =
    9670          90 :         cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
    9671          90 :     return std::pair(poRet, poRet);
    9672             : }
    9673             : 
    9674             : /************************************************************************/
    9675             : /*                       CommitTransaction()                            */
    9676             : /************************************************************************/
    9677             : 
    9678         176 : OGRErr GDALGeoPackageDataset::CommitTransaction()
    9679             : 
    9680             : {
    9681         176 :     if (nSoftTransactionLevel == 1)
    9682             :     {
    9683         175 :         FlushMetadata();
    9684         397 :         for (int i = 0; i < m_nLayers; i++)
    9685             :         {
    9686         222 :             m_papoLayers[i]->DoJobAtTransactionCommit();
    9687             :         }
    9688             :     }
    9689             : 
    9690         176 :     return OGRSQLiteBaseDataSource::CommitTransaction();
    9691             : }
    9692             : 
    9693             : /************************************************************************/
    9694             : /*                     RollbackTransaction()                            */
    9695             : /************************************************************************/
    9696             : 
    9697          34 : OGRErr GDALGeoPackageDataset::RollbackTransaction()
    9698             : 
    9699             : {
    9700             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9701          68 :     std::vector<bool> abAddTriggers;
    9702          34 :     std::vector<bool> abTriggersDeletedInTransaction;
    9703             : #endif
    9704          34 :     if (nSoftTransactionLevel == 1)
    9705             :     {
    9706          33 :         FlushMetadata();
    9707          68 :         for (int i = 0; i < m_nLayers; i++)
    9708             :         {
    9709             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9710          35 :             abAddTriggers.push_back(
    9711          35 :                 m_papoLayers[i]->GetAddOGRFeatureCountTriggers());
    9712          35 :             abTriggersDeletedInTransaction.push_back(
    9713          35 :                 m_papoLayers[i]
    9714          35 :                     ->GetOGRFeatureCountTriggersDeletedInTransaction());
    9715          35 :             m_papoLayers[i]->SetAddOGRFeatureCountTriggers(false);
    9716             : #endif
    9717          35 :             m_papoLayers[i]->DoJobAtTransactionRollback();
    9718             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9719          35 :             m_papoLayers[i]->DisableFeatureCount();
    9720             : #endif
    9721             :         }
    9722             :     }
    9723             : 
    9724          34 :     const OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
    9725             : 
    9726             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9727          34 :     if (!abAddTriggers.empty())
    9728             :     {
    9729          66 :         for (int i = 0; i < m_nLayers; i++)
    9730             :         {
    9731          35 :             if (abTriggersDeletedInTransaction[i])
    9732             :             {
    9733           7 :                 m_papoLayers[i]->SetOGRFeatureCountTriggersEnabled(true);
    9734             :             }
    9735             :             else
    9736             :             {
    9737          28 :                 m_papoLayers[i]->SetAddOGRFeatureCountTriggers(
    9738          56 :                     abAddTriggers[i]);
    9739             :             }
    9740             :         }
    9741             :     }
    9742             : #endif
    9743          68 :     return eErr;
    9744             : }
    9745             : 
    9746             : /************************************************************************/
    9747             : /*                       GetGeometryTypeString()                        */
    9748             : /************************************************************************/
    9749             : 
    9750             : const char *
    9751        1303 : GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
    9752             : {
    9753        1303 :     const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
    9754        1315 :     if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
    9755          12 :         CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
    9756             :     {
    9757           0 :         pszGPKGGeomType = "GEOMCOLLECTION";
    9758             :     }
    9759        1303 :     return pszGPKGGeomType;
    9760             : }
    9761             : 
    9762             : /************************************************************************/
    9763             : /*                           GetFieldDomainNames()                      */
    9764             : /************************************************************************/
    9765             : 
    9766             : std::vector<std::string>
    9767          10 : GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
    9768             : {
    9769          10 :     if (!HasDataColumnConstraintsTable())
    9770           3 :         return std::vector<std::string>();
    9771             : 
    9772          14 :     std::vector<std::string> oDomainNamesList;
    9773             : 
    9774           7 :     std::unique_ptr<SQLResult> oResultTable;
    9775             :     {
    9776             :         std::string osSQL =
    9777             :             "SELECT DISTINCT constraint_name "
    9778             :             "FROM gpkg_data_column_constraints "
    9779             :             "WHERE constraint_name NOT LIKE '_%_domain_description' "
    9780             :             "ORDER BY constraint_name "
    9781           7 :             "LIMIT 10000"  // to avoid denial of service
    9782             :             ;
    9783           7 :         oResultTable = SQLQuery(hDB, osSQL.c_str());
    9784           7 :         if (!oResultTable)
    9785           0 :             return oDomainNamesList;
    9786             :     }
    9787             : 
    9788           7 :     if (oResultTable->RowCount() == 10000)
    9789             :     {
    9790           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9791             :                  "Number of rows returned for field domain names has been "
    9792             :                  "truncated.");
    9793             :     }
    9794           7 :     else if (oResultTable->RowCount() > 0)
    9795             :     {
    9796           7 :         oDomainNamesList.reserve(oResultTable->RowCount());
    9797          89 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    9798             :         {
    9799          82 :             const char *pszConstraintName = oResultTable->GetValue(0, i);
    9800          82 :             if (!pszConstraintName)
    9801           0 :                 continue;
    9802             : 
    9803          82 :             oDomainNamesList.emplace_back(pszConstraintName);
    9804             :         }
    9805             :     }
    9806             : 
    9807           7 :     return oDomainNamesList;
    9808             : }
    9809             : 
    9810             : /************************************************************************/
    9811             : /*                           GetFieldDomain()                           */
    9812             : /************************************************************************/
    9813             : 
    9814             : const OGRFieldDomain *
    9815         102 : GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
    9816             : {
    9817         102 :     const auto baseRet = GDALDataset::GetFieldDomain(name);
    9818         102 :     if (baseRet)
    9819          42 :         return baseRet;
    9820             : 
    9821          60 :     if (!HasDataColumnConstraintsTable())
    9822           4 :         return nullptr;
    9823             : 
    9824          56 :     const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
    9825          56 :     const char *min_is_inclusive =
    9826          56 :         bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
    9827          56 :     const char *max_is_inclusive =
    9828          56 :         bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
    9829             : 
    9830          56 :     std::unique_ptr<SQLResult> oResultTable;
    9831             :     // Note: for coded domains, we use a little trick by using a dummy
    9832             :     // _{domainname}_domain_description enum that has a single entry whose
    9833             :     // description is the description of the main domain.
    9834             :     {
    9835          56 :         char *pszSQL = sqlite3_mprintf(
    9836             :             "SELECT constraint_type, value, min, %s, "
    9837             :             "max, %s, description, constraint_name "
    9838             :             "FROM gpkg_data_column_constraints "
    9839             :             "WHERE constraint_name IN ('%q', "
    9840             :             "'_%q_domain_description') "
    9841             :             "AND length(constraint_type) < 100 "  // to
    9842             :                                                   // avoid
    9843             :                                                   // denial
    9844             :                                                   // of
    9845             :                                                   // service
    9846             :             "AND (value IS NULL OR length(value) < "
    9847             :             "10000) "  // to avoid denial
    9848             :                        // of service
    9849             :             "AND (description IS NULL OR "
    9850             :             "length(description) < 10000) "  // to
    9851             :                                              // avoid
    9852             :                                              // denial
    9853             :                                              // of
    9854             :                                              // service
    9855             :             "ORDER BY value "
    9856             :             "LIMIT 10000",  // to avoid denial of
    9857             :                             // service
    9858             :             min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
    9859          56 :         oResultTable = SQLQuery(hDB, pszSQL);
    9860          56 :         sqlite3_free(pszSQL);
    9861          56 :         if (!oResultTable)
    9862           0 :             return nullptr;
    9863             :     }
    9864          56 :     if (oResultTable->RowCount() == 0)
    9865             :     {
    9866          15 :         return nullptr;
    9867             :     }
    9868          41 :     if (oResultTable->RowCount() == 10000)
    9869             :     {
    9870           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9871             :                  "Number of rows returned for field domain %s has been "
    9872             :                  "truncated.",
    9873             :                  name.c_str());
    9874             :     }
    9875             : 
    9876             :     // Try to find the field domain data type from fields that implement it
    9877          41 :     int nFieldType = -1;
    9878          41 :     OGRFieldSubType eSubType = OFSTNone;
    9879          41 :     if (HasDataColumnsTable())
    9880             :     {
    9881          36 :         char *pszSQL = sqlite3_mprintf(
    9882             :             "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
    9883             :             "constraint_name = '%q' LIMIT 10",
    9884             :             name.c_str());
    9885          72 :         auto oResultTable2 = SQLQuery(hDB, pszSQL);
    9886          36 :         sqlite3_free(pszSQL);
    9887          36 :         if (oResultTable2 && oResultTable2->RowCount() >= 1)
    9888             :         {
    9889          46 :             for (int iRecord = 0; iRecord < oResultTable2->RowCount();
    9890             :                  iRecord++)
    9891             :             {
    9892          23 :                 const char *pszTableName = oResultTable2->GetValue(0, iRecord);
    9893          23 :                 const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
    9894          23 :                 if (pszTableName == nullptr || pszColumnName == nullptr)
    9895           0 :                     continue;
    9896             :                 OGRLayer *poLayer =
    9897          46 :                     const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
    9898          23 :                         pszTableName);
    9899          23 :                 if (poLayer)
    9900             :                 {
    9901          23 :                     const auto poFDefn = poLayer->GetLayerDefn();
    9902          23 :                     int nIdx = poFDefn->GetFieldIndex(pszColumnName);
    9903          23 :                     if (nIdx >= 0)
    9904             :                     {
    9905          23 :                         const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
    9906          23 :                         const auto eType = poFieldDefn->GetType();
    9907          23 :                         if (nFieldType < 0)
    9908             :                         {
    9909          23 :                             nFieldType = eType;
    9910          23 :                             eSubType = poFieldDefn->GetSubType();
    9911             :                         }
    9912           0 :                         else if ((eType == OFTInteger64 || eType == OFTReal) &&
    9913             :                                  nFieldType == OFTInteger)
    9914             :                         {
    9915             :                             // ok
    9916             :                         }
    9917           0 :                         else if (eType == OFTInteger &&
    9918           0 :                                  (nFieldType == OFTInteger64 ||
    9919             :                                   nFieldType == OFTReal))
    9920             :                         {
    9921           0 :                             nFieldType = OFTInteger;
    9922           0 :                             eSubType = OFSTNone;
    9923             :                         }
    9924           0 :                         else if (nFieldType != eType)
    9925             :                         {
    9926           0 :                             nFieldType = -1;
    9927           0 :                             eSubType = OFSTNone;
    9928           0 :                             break;
    9929             :                         }
    9930             :                     }
    9931             :                 }
    9932             :             }
    9933             :         }
    9934             :     }
    9935             : 
    9936          41 :     std::unique_ptr<OGRFieldDomain> poDomain;
    9937          82 :     std::vector<OGRCodedValue> asValues;
    9938          41 :     bool error = false;
    9939          82 :     CPLString osLastConstraintType;
    9940          41 :     int nFieldTypeFromEnumCode = -1;
    9941          82 :     std::string osConstraintDescription;
    9942          82 :     std::string osDescrConstraintName("_");
    9943          41 :     osDescrConstraintName += name;
    9944          41 :     osDescrConstraintName += "_domain_description";
    9945         100 :     for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
    9946             :     {
    9947          63 :         const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
    9948          63 :         if (pszConstraintType == nullptr)
    9949           0 :             continue;
    9950          63 :         const char *pszValue = oResultTable->GetValue(1, iRecord);
    9951          63 :         const char *pszMin = oResultTable->GetValue(2, iRecord);
    9952             :         const bool bIsMinIncluded =
    9953          63 :             oResultTable->GetValueAsInteger(3, iRecord) == 1;
    9954          63 :         const char *pszMax = oResultTable->GetValue(4, iRecord);
    9955             :         const bool bIsMaxIncluded =
    9956          63 :             oResultTable->GetValueAsInteger(5, iRecord) == 1;
    9957          63 :         const char *pszDescription = oResultTable->GetValue(6, iRecord);
    9958          63 :         const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
    9959             : 
    9960          63 :         if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
    9961             :         {
    9962           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    9963             :                      "Only constraint of type 'enum' can have multiple rows");
    9964           1 :             error = true;
    9965           1 :             break;
    9966             :         }
    9967             : 
    9968          62 :         if (strcmp(pszConstraintType, "enum") == 0)
    9969             :         {
    9970          42 :             if (pszValue == nullptr)
    9971             :             {
    9972           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    9973             :                          "NULL in 'value' column of enumeration");
    9974           1 :                 error = true;
    9975           1 :                 break;
    9976             :             }
    9977          41 :             if (osDescrConstraintName == pszConstraintName)
    9978             :             {
    9979           1 :                 if (pszDescription)
    9980             :                 {
    9981           1 :                     osConstraintDescription = pszDescription;
    9982             :                 }
    9983           1 :                 continue;
    9984             :             }
    9985          40 :             if (asValues.empty())
    9986             :             {
    9987          20 :                 asValues.reserve(oResultTable->RowCount() + 1);
    9988             :             }
    9989             :             OGRCodedValue cv;
    9990             :             // intended: the 'value' column in GPKG is actually the code
    9991          40 :             cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
    9992          40 :             if (cv.pszCode == nullptr)
    9993             :             {
    9994           0 :                 error = true;
    9995           0 :                 break;
    9996             :             }
    9997          40 :             if (pszDescription)
    9998             :             {
    9999          29 :                 cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
   10000          29 :                 if (cv.pszValue == nullptr)
   10001             :                 {
   10002           0 :                     VSIFree(cv.pszCode);
   10003           0 :                     error = true;
   10004           0 :                     break;
   10005             :                 }
   10006             :             }
   10007             :             else
   10008             :             {
   10009          11 :                 cv.pszValue = nullptr;
   10010             :             }
   10011             : 
   10012             :             // If we can't get the data type from field definition, guess it
   10013             :             // from code.
   10014          40 :             if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
   10015             :             {
   10016          18 :                 switch (CPLGetValueType(cv.pszCode))
   10017             :                 {
   10018          13 :                     case CPL_VALUE_INTEGER:
   10019             :                     {
   10020          13 :                         if (nFieldTypeFromEnumCode != OFTReal &&
   10021             :                             nFieldTypeFromEnumCode != OFTInteger64)
   10022             :                         {
   10023           9 :                             const auto nVal = CPLAtoGIntBig(cv.pszCode);
   10024          17 :                             if (nVal < std::numeric_limits<int>::min() ||
   10025           8 :                                 nVal > std::numeric_limits<int>::max())
   10026             :                             {
   10027           3 :                                 nFieldTypeFromEnumCode = OFTInteger64;
   10028             :                             }
   10029             :                             else
   10030             :                             {
   10031           6 :                                 nFieldTypeFromEnumCode = OFTInteger;
   10032             :                             }
   10033             :                         }
   10034          13 :                         break;
   10035             :                     }
   10036             : 
   10037           3 :                     case CPL_VALUE_REAL:
   10038           3 :                         nFieldTypeFromEnumCode = OFTReal;
   10039           3 :                         break;
   10040             : 
   10041           2 :                     case CPL_VALUE_STRING:
   10042           2 :                         nFieldTypeFromEnumCode = OFTString;
   10043           2 :                         break;
   10044             :                 }
   10045             :             }
   10046             : 
   10047          40 :             asValues.emplace_back(cv);
   10048             :         }
   10049          20 :         else if (strcmp(pszConstraintType, "range") == 0)
   10050             :         {
   10051             :             OGRField sMin;
   10052             :             OGRField sMax;
   10053          14 :             OGR_RawField_SetUnset(&sMin);
   10054          14 :             OGR_RawField_SetUnset(&sMax);
   10055          14 :             if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
   10056           8 :                 nFieldType = OFTReal;
   10057          27 :             if (pszMin != nullptr &&
   10058          13 :                 CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
   10059             :             {
   10060          10 :                 if (nFieldType == OFTInteger)
   10061           3 :                     sMin.Integer = atoi(pszMin);
   10062           7 :                 else if (nFieldType == OFTInteger64)
   10063           3 :                     sMin.Integer64 = CPLAtoGIntBig(pszMin);
   10064             :                 else /* if( nFieldType == OFTReal ) */
   10065           4 :                     sMin.Real = CPLAtof(pszMin);
   10066             :             }
   10067          27 :             if (pszMax != nullptr &&
   10068          13 :                 CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
   10069             :             {
   10070          10 :                 if (nFieldType == OFTInteger)
   10071           3 :                     sMax.Integer = atoi(pszMax);
   10072           7 :                 else if (nFieldType == OFTInteger64)
   10073           3 :                     sMax.Integer64 = CPLAtoGIntBig(pszMax);
   10074             :                 else /* if( nFieldType == OFTReal ) */
   10075           4 :                     sMax.Real = CPLAtof(pszMax);
   10076             :             }
   10077          28 :             poDomain.reset(new OGRRangeFieldDomain(
   10078             :                 name, pszDescription ? pszDescription : "",
   10079             :                 static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
   10080          14 :                 bIsMinIncluded, sMax, bIsMaxIncluded));
   10081             :         }
   10082           6 :         else if (strcmp(pszConstraintType, "glob") == 0)
   10083             :         {
   10084           5 :             if (pszValue == nullptr)
   10085             :             {
   10086           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10087             :                          "NULL in 'value' column of glob");
   10088           1 :                 error = true;
   10089           1 :                 break;
   10090             :             }
   10091           4 :             if (nFieldType < 0)
   10092           1 :                 nFieldType = OFTString;
   10093           8 :             poDomain.reset(new OGRGlobFieldDomain(
   10094             :                 name, pszDescription ? pszDescription : "",
   10095           4 :                 static_cast<OGRFieldType>(nFieldType), eSubType, pszValue));
   10096             :         }
   10097             :         else
   10098             :         {
   10099           1 :             CPLError(CE_Failure, CPLE_AppDefined,
   10100             :                      "Unhandled constraint_type: %s", pszConstraintType);
   10101           1 :             error = true;
   10102           1 :             break;
   10103             :         }
   10104             : 
   10105          58 :         osLastConstraintType = pszConstraintType;
   10106             :     }
   10107             : 
   10108          41 :     if (!asValues.empty())
   10109             :     {
   10110          20 :         if (nFieldType < 0)
   10111           9 :             nFieldType = nFieldTypeFromEnumCode;
   10112          20 :         poDomain.reset(
   10113             :             new OGRCodedFieldDomain(name, osConstraintDescription,
   10114             :                                     static_cast<OGRFieldType>(nFieldType),
   10115          20 :                                     eSubType, std::move(asValues)));
   10116             :     }
   10117             : 
   10118          41 :     if (error)
   10119             :     {
   10120           4 :         return nullptr;
   10121             :     }
   10122             : 
   10123          37 :     m_oMapFieldDomains[name] = std::move(poDomain);
   10124          37 :     return GDALDataset::GetFieldDomain(name);
   10125             : }
   10126             : 
   10127             : /************************************************************************/
   10128             : /*                           AddFieldDomain()                           */
   10129             : /************************************************************************/
   10130             : 
   10131          18 : bool GDALGeoPackageDataset::AddFieldDomain(
   10132             :     std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
   10133             : {
   10134          36 :     const std::string domainName(domain->GetName());
   10135          18 :     if (!GetUpdate())
   10136             :     {
   10137           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10138             :                  "AddFieldDomain() not supported on read-only dataset");
   10139           0 :         return false;
   10140             :     }
   10141          18 :     if (GetFieldDomain(domainName) != nullptr)
   10142             :     {
   10143           1 :         failureReason = "A domain of identical name already exists";
   10144           1 :         return false;
   10145             :     }
   10146          17 :     if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
   10147           0 :         return false;
   10148             : 
   10149          17 :     const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
   10150          17 :     const char *min_is_inclusive =
   10151          17 :         bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
   10152          17 :     const char *max_is_inclusive =
   10153          17 :         bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
   10154             : 
   10155          17 :     const auto &osDescription = domain->GetDescription();
   10156          17 :     switch (domain->GetDomainType())
   10157             :     {
   10158          11 :         case OFDT_CODED:
   10159             :         {
   10160             :             const auto poCodedDomain =
   10161          11 :                 cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
   10162          11 :             if (!osDescription.empty())
   10163             :             {
   10164             :                 // We use a little trick by using a dummy
   10165             :                 // _{domainname}_domain_description enum that has a single
   10166             :                 // entry whose description is the description of the main
   10167             :                 // domain.
   10168           1 :                 char *pszSQL = sqlite3_mprintf(
   10169             :                     "INSERT INTO gpkg_data_column_constraints ("
   10170             :                     "constraint_name, constraint_type, value, "
   10171             :                     "min, %s, max, %s, "
   10172             :                     "description) VALUES ("
   10173             :                     "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
   10174             :                     "NULL, %Q)",
   10175             :                     min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10176             :                     osDescription.c_str());
   10177           1 :                 CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
   10178           1 :                 sqlite3_free(pszSQL);
   10179             :             }
   10180          11 :             const auto &enumeration = poCodedDomain->GetEnumeration();
   10181          33 :             for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
   10182             :             {
   10183          22 :                 char *pszSQL = sqlite3_mprintf(
   10184             :                     "INSERT INTO gpkg_data_column_constraints ("
   10185             :                     "constraint_name, constraint_type, value, "
   10186             :                     "min, %s, max, %s, "
   10187             :                     "description) VALUES ("
   10188             :                     "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
   10189             :                     min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10190          22 :                     enumeration[i].pszCode, enumeration[i].pszValue);
   10191          22 :                 bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
   10192          22 :                 sqlite3_free(pszSQL);
   10193          22 :                 if (!ok)
   10194           0 :                     return false;
   10195             :             }
   10196          11 :             break;
   10197             :         }
   10198             : 
   10199           5 :         case OFDT_RANGE:
   10200             :         {
   10201             :             const auto poRangeDomain =
   10202           5 :                 cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
   10203           5 :             const auto eFieldType = poRangeDomain->GetFieldType();
   10204           5 :             if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
   10205             :                 eFieldType != OFTReal)
   10206             :             {
   10207             :                 failureReason = "Only range domains of numeric type are "
   10208           0 :                                 "supported in GeoPackage";
   10209           0 :                 return false;
   10210             :             }
   10211             : 
   10212           5 :             double dfMin = -std::numeric_limits<double>::infinity();
   10213           5 :             double dfMax = std::numeric_limits<double>::infinity();
   10214           5 :             bool bMinIsInclusive = true;
   10215           5 :             const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
   10216           5 :             bool bMaxIsInclusive = true;
   10217           5 :             const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
   10218           5 :             if (eFieldType == OFTInteger)
   10219             :             {
   10220           1 :                 if (!OGR_RawField_IsUnset(&sMin))
   10221           1 :                     dfMin = sMin.Integer;
   10222           1 :                 if (!OGR_RawField_IsUnset(&sMax))
   10223           1 :                     dfMax = sMax.Integer;
   10224             :             }
   10225           4 :             else if (eFieldType == OFTInteger64)
   10226             :             {
   10227           1 :                 if (!OGR_RawField_IsUnset(&sMin))
   10228           1 :                     dfMin = static_cast<double>(sMin.Integer64);
   10229           1 :                 if (!OGR_RawField_IsUnset(&sMax))
   10230           1 :                     dfMax = static_cast<double>(sMax.Integer64);
   10231             :             }
   10232             :             else /* if( eFieldType == OFTReal ) */
   10233             :             {
   10234           3 :                 if (!OGR_RawField_IsUnset(&sMin))
   10235           3 :                     dfMin = sMin.Real;
   10236           3 :                 if (!OGR_RawField_IsUnset(&sMax))
   10237           3 :                     dfMax = sMax.Real;
   10238             :             }
   10239             : 
   10240           5 :             sqlite3_stmt *hInsertStmt = nullptr;
   10241             :             const char *pszSQL =
   10242           5 :                 CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
   10243             :                            "constraint_name, constraint_type, value, "
   10244             :                            "min, %s, max, %s, "
   10245             :                            "description) VALUES ("
   10246             :                            "?, 'range', NULL, ?, ?, ?, ?, ?)",
   10247             :                            min_is_inclusive, max_is_inclusive);
   10248           5 :             if (sqlite3_prepare_v2(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
   10249             :                 SQLITE_OK)
   10250             :             {
   10251           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10252             :                          "failed to prepare SQL: %s", pszSQL);
   10253           0 :                 return false;
   10254             :             }
   10255           5 :             sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
   10256           5 :                               static_cast<int>(domainName.size()),
   10257             :                               SQLITE_TRANSIENT);
   10258           5 :             sqlite3_bind_double(hInsertStmt, 2, dfMin);
   10259           5 :             sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
   10260           5 :             sqlite3_bind_double(hInsertStmt, 4, dfMax);
   10261           5 :             sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
   10262           5 :             if (osDescription.empty())
   10263             :             {
   10264           3 :                 sqlite3_bind_null(hInsertStmt, 6);
   10265             :             }
   10266             :             else
   10267             :             {
   10268           2 :                 sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
   10269           2 :                                   static_cast<int>(osDescription.size()),
   10270             :                                   SQLITE_TRANSIENT);
   10271             :             }
   10272           5 :             const int sqlite_err = sqlite3_step(hInsertStmt);
   10273           5 :             sqlite3_finalize(hInsertStmt);
   10274           5 :             if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
   10275             :             {
   10276           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10277             :                          "failed to execute insertion: %s",
   10278             :                          sqlite3_errmsg(hDB));
   10279           0 :                 return false;
   10280             :             }
   10281             : 
   10282           5 :             break;
   10283             :         }
   10284             : 
   10285           1 :         case OFDT_GLOB:
   10286             :         {
   10287             :             const auto poGlobDomain =
   10288           1 :                 cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
   10289           2 :             char *pszSQL = sqlite3_mprintf(
   10290             :                 "INSERT INTO gpkg_data_column_constraints ("
   10291             :                 "constraint_name, constraint_type, value, "
   10292             :                 "min, %s, max, %s, "
   10293             :                 "description) VALUES ("
   10294             :                 "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
   10295             :                 min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10296           1 :                 poGlobDomain->GetGlob().c_str(),
   10297           2 :                 osDescription.empty() ? nullptr : osDescription.c_str());
   10298           1 :             bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
   10299           1 :             sqlite3_free(pszSQL);
   10300           1 :             if (!ok)
   10301           0 :                 return false;
   10302             : 
   10303           1 :             break;
   10304             :         }
   10305             :     }
   10306             : 
   10307          17 :     m_oMapFieldDomains[domainName] = std::move(domain);
   10308          17 :     return true;
   10309             : }
   10310             : 
   10311             : /************************************************************************/
   10312             : /*                          AddRelationship()                           */
   10313             : /************************************************************************/
   10314             : 
   10315          24 : bool GDALGeoPackageDataset::AddRelationship(
   10316             :     std::unique_ptr<GDALRelationship> &&relationship,
   10317             :     std::string &failureReason)
   10318             : {
   10319          24 :     if (!GetUpdate())
   10320             :     {
   10321           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10322             :                  "AddRelationship() not supported on read-only dataset");
   10323           0 :         return false;
   10324             :     }
   10325             : 
   10326             :     const std::string osRelationshipName = GenerateNameForRelationship(
   10327          24 :         relationship->GetLeftTableName().c_str(),
   10328          24 :         relationship->GetRightTableName().c_str(),
   10329          96 :         relationship->GetRelatedTableType().c_str());
   10330             :     // sanity checks
   10331          24 :     if (GetRelationship(osRelationshipName) != nullptr)
   10332             :     {
   10333           1 :         failureReason = "A relationship of identical name already exists";
   10334           1 :         return false;
   10335             :     }
   10336             : 
   10337          23 :     if (!ValidateRelationship(relationship.get(), failureReason))
   10338             :     {
   10339          14 :         return false;
   10340             :     }
   10341             : 
   10342           9 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
   10343             :     {
   10344           0 :         return false;
   10345             :     }
   10346           9 :     if (!CreateRelationsTableIfNecessary())
   10347             :     {
   10348           0 :         failureReason = "Could not create gpkgext_relations table";
   10349           0 :         return false;
   10350             :     }
   10351           9 :     if (SQLGetInteger(GetDB(),
   10352             :                       "SELECT 1 FROM gpkg_extensions WHERE "
   10353             :                       "table_name = 'gpkgext_relations'",
   10354           9 :                       nullptr) != 1)
   10355             :     {
   10356           4 :         if (OGRERR_NONE !=
   10357           4 :             SQLCommand(
   10358             :                 GetDB(),
   10359             :                 "INSERT INTO gpkg_extensions "
   10360             :                 "(table_name,column_name,extension_name,definition,scope) "
   10361             :                 "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
   10362             :                 "'http://www.geopackage.org/18-000.html', "
   10363             :                 "'read-write')"))
   10364             :         {
   10365             :             failureReason =
   10366           0 :                 "Could not create gpkg_extensions entry for gpkgext_relations";
   10367           0 :             return false;
   10368             :         }
   10369             :     }
   10370             : 
   10371           9 :     const std::string &osLeftTableName = relationship->GetLeftTableName();
   10372           9 :     const std::string &osRightTableName = relationship->GetRightTableName();
   10373           9 :     const auto &aosLeftTableFields = relationship->GetLeftTableFields();
   10374           9 :     const auto &aosRightTableFields = relationship->GetRightTableFields();
   10375             : 
   10376          18 :     std::string osRelatedTableType = relationship->GetRelatedTableType();
   10377           9 :     if (osRelatedTableType.empty())
   10378             :     {
   10379           5 :         osRelatedTableType = "features";
   10380             :     }
   10381             : 
   10382             :     // generate mapping table if not set
   10383          18 :     CPLString osMappingTableName = relationship->GetMappingTableName();
   10384           9 :     if (osMappingTableName.empty())
   10385             :     {
   10386           3 :         int nIndex = 1;
   10387           3 :         osMappingTableName = osLeftTableName + "_" + osRightTableName;
   10388           3 :         while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
   10389             :         {
   10390           0 :             nIndex += 1;
   10391             :             osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
   10392           0 :                                       osRightTableName.c_str(), nIndex);
   10393             :         }
   10394             : 
   10395             :         // determine whether base/related keys are unique
   10396           3 :         bool bBaseKeyIsUnique = false;
   10397             :         {
   10398             :             const std::set<std::string> uniqueBaseFieldsUC =
   10399             :                 SQLGetUniqueFieldUCConstraints(GetDB(),
   10400           6 :                                                osLeftTableName.c_str());
   10401           6 :             if (uniqueBaseFieldsUC.find(
   10402           3 :                     CPLString(aosLeftTableFields[0]).toupper()) !=
   10403           6 :                 uniqueBaseFieldsUC.end())
   10404             :             {
   10405           2 :                 bBaseKeyIsUnique = true;
   10406             :             }
   10407             :         }
   10408           3 :         bool bRelatedKeyIsUnique = false;
   10409             :         {
   10410             :             const std::set<std::string> uniqueRelatedFieldsUC =
   10411             :                 SQLGetUniqueFieldUCConstraints(GetDB(),
   10412           6 :                                                osRightTableName.c_str());
   10413           6 :             if (uniqueRelatedFieldsUC.find(
   10414           3 :                     CPLString(aosRightTableFields[0]).toupper()) !=
   10415           6 :                 uniqueRelatedFieldsUC.end())
   10416             :             {
   10417           2 :                 bRelatedKeyIsUnique = true;
   10418             :             }
   10419             :         }
   10420             : 
   10421             :         // create mapping table
   10422             : 
   10423           3 :         std::string osBaseIdDefinition = "base_id INTEGER";
   10424           3 :         if (bBaseKeyIsUnique)
   10425             :         {
   10426           2 :             char *pszSQL = sqlite3_mprintf(
   10427             :                 " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
   10428             :                 "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
   10429             :                 "DEFERRED",
   10430             :                 osMappingTableName.c_str(), osLeftTableName.c_str(),
   10431           2 :                 aosLeftTableFields[0].c_str());
   10432           2 :             osBaseIdDefinition += pszSQL;
   10433           2 :             sqlite3_free(pszSQL);
   10434             :         }
   10435             : 
   10436           3 :         std::string osRelatedIdDefinition = "related_id INTEGER";
   10437           3 :         if (bRelatedKeyIsUnique)
   10438             :         {
   10439           2 :             char *pszSQL = sqlite3_mprintf(
   10440             :                 " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
   10441             :                 "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
   10442             :                 "DEFERRED",
   10443             :                 osMappingTableName.c_str(), osRightTableName.c_str(),
   10444           2 :                 aosRightTableFields[0].c_str());
   10445           2 :             osRelatedIdDefinition += pszSQL;
   10446           2 :             sqlite3_free(pszSQL);
   10447             :         }
   10448             : 
   10449           3 :         char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
   10450             :                                        "id INTEGER PRIMARY KEY AUTOINCREMENT, "
   10451             :                                        "%s, %s);",
   10452             :                                        osMappingTableName.c_str(),
   10453             :                                        osBaseIdDefinition.c_str(),
   10454             :                                        osRelatedIdDefinition.c_str());
   10455           3 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
   10456           3 :         sqlite3_free(pszSQL);
   10457           3 :         if (eErr != OGRERR_NONE)
   10458             :         {
   10459             :             failureReason =
   10460           0 :                 ("Could not create mapping table " + osMappingTableName)
   10461           0 :                     .c_str();
   10462           0 :             return false;
   10463             :         }
   10464             : 
   10465             :         /*
   10466             :          * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
   10467             :          * The related tables extension explicitly states that the mapping table should only be
   10468             :          * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
   10469             :          * https://github.com/opengeospatial/geopackage/issues/679).
   10470             :          *
   10471             :          * However, if we don't insert the mapping table into gpkg_contents then it is no longer
   10472             :          * visible to some clients (eg ESRI software only allows opening tables that are present
   10473             :          * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
   10474             :          *
   10475             :          * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
   10476             :          */
   10477           3 :         pszSQL = sqlite3_mprintf(
   10478             :             "INSERT INTO gpkg_contents "
   10479             :             "(table_name,data_type,identifier,description,last_change,srs_id) "
   10480             :             "VALUES "
   10481             :             "('%q','attributes','%q','Mapping table for relationship between "
   10482             :             "%q and %q',%s,0)",
   10483             :             osMappingTableName.c_str(), /*table_name*/
   10484             :             osMappingTableName.c_str(), /*identifier*/
   10485             :             osLeftTableName.c_str(),    /*description left table name*/
   10486             :             osRightTableName.c_str(),   /*description right table name*/
   10487           6 :             GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
   10488             : 
   10489             :         // Note -- we explicitly ignore failures here, because hey, we aren't really
   10490             :         // supposed to be adding this table to gpkg_contents anyway!
   10491           3 :         (void)SQLCommand(hDB, pszSQL);
   10492           3 :         sqlite3_free(pszSQL);
   10493             : 
   10494           3 :         pszSQL = sqlite3_mprintf(
   10495             :             "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
   10496             :             osMappingTableName.c_str(), osMappingTableName.c_str());
   10497           3 :         eErr = SQLCommand(hDB, pszSQL);
   10498           3 :         sqlite3_free(pszSQL);
   10499           3 :         if (eErr != OGRERR_NONE)
   10500             :         {
   10501           0 :             failureReason = ("Could not create index for " +
   10502           0 :                              osMappingTableName + " (base_id)")
   10503           0 :                                 .c_str();
   10504           0 :             return false;
   10505             :         }
   10506             : 
   10507           3 :         pszSQL = sqlite3_mprintf(
   10508             :             "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
   10509             :             osMappingTableName.c_str(), osMappingTableName.c_str());
   10510           3 :         eErr = SQLCommand(hDB, pszSQL);
   10511           3 :         sqlite3_free(pszSQL);
   10512           3 :         if (eErr != OGRERR_NONE)
   10513             :         {
   10514           0 :             failureReason = ("Could not create index for " +
   10515           0 :                              osMappingTableName + " (related_id)")
   10516           0 :                                 .c_str();
   10517           0 :             return false;
   10518             :         }
   10519             :     }
   10520             :     else
   10521             :     {
   10522             :         // validate mapping table structure
   10523           6 :         if (OGRGeoPackageTableLayer *poLayer =
   10524           6 :                 cpl::down_cast<OGRGeoPackageTableLayer *>(
   10525           6 :                     GetLayerByName(osMappingTableName)))
   10526             :         {
   10527           4 :             if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
   10528             :             {
   10529             :                 failureReason =
   10530           2 :                     ("Field base_id must exist in " + osMappingTableName)
   10531           1 :                         .c_str();
   10532           1 :                 return false;
   10533             :             }
   10534           3 :             if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
   10535             :             {
   10536             :                 failureReason =
   10537           2 :                     ("Field related_id must exist in " + osMappingTableName)
   10538           1 :                         .c_str();
   10539           1 :                 return false;
   10540             :             }
   10541             :         }
   10542             :         else
   10543             :         {
   10544             :             failureReason =
   10545           2 :                 ("Could not retrieve table " + osMappingTableName).c_str();
   10546           2 :             return false;
   10547             :         }
   10548             :     }
   10549             : 
   10550           5 :     char *pszSQL = sqlite3_mprintf(
   10551             :         "INSERT INTO gpkg_extensions "
   10552             :         "(table_name,column_name,extension_name,definition,scope) "
   10553             :         "VALUES ('%q', NULL, 'gpkg_related_tables', "
   10554             :         "'http://www.geopackage.org/18-000.html', "
   10555             :         "'read-write')",
   10556             :         osMappingTableName.c_str());
   10557           5 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
   10558           5 :     sqlite3_free(pszSQL);
   10559           5 :     if (eErr != OGRERR_NONE)
   10560             :     {
   10561           0 :         failureReason = ("Could not insert mapping table " +
   10562           0 :                          osMappingTableName + " into gpkg_extensions")
   10563           0 :                             .c_str();
   10564           0 :         return false;
   10565             :     }
   10566             : 
   10567          15 :     pszSQL = sqlite3_mprintf(
   10568             :         "INSERT INTO gpkgext_relations "
   10569             :         "(base_table_name,base_primary_column,related_table_name,related_"
   10570             :         "primary_column,relation_name,mapping_table_name) "
   10571             :         "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
   10572           5 :         osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
   10573           5 :         osRightTableName.c_str(), aosRightTableFields[0].c_str(),
   10574             :         osRelatedTableType.c_str(), osMappingTableName.c_str());
   10575           5 :     eErr = SQLCommand(hDB, pszSQL);
   10576           5 :     sqlite3_free(pszSQL);
   10577           5 :     if (eErr != OGRERR_NONE)
   10578             :     {
   10579           0 :         failureReason = "Could not insert relationship into gpkgext_relations";
   10580           0 :         return false;
   10581             :     }
   10582             : 
   10583           5 :     ClearCachedRelationships();
   10584           5 :     LoadRelationships();
   10585           5 :     return true;
   10586             : }
   10587             : 
   10588             : /************************************************************************/
   10589             : /*                         DeleteRelationship()                         */
   10590             : /************************************************************************/
   10591             : 
   10592           4 : bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
   10593             :                                                std::string &failureReason)
   10594             : {
   10595           4 :     if (eAccess != GA_Update)
   10596             :     {
   10597           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10598             :                  "DeleteRelationship() not supported on read-only dataset");
   10599           0 :         return false;
   10600             :     }
   10601             : 
   10602             :     // ensure relationships are up to date before we try to remove one
   10603           4 :     ClearCachedRelationships();
   10604           4 :     LoadRelationships();
   10605             : 
   10606           8 :     std::string osMappingTableName;
   10607             :     {
   10608           4 :         const GDALRelationship *poRelationship = GetRelationship(name);
   10609           4 :         if (poRelationship == nullptr)
   10610             :         {
   10611           1 :             failureReason = "Could not find relationship with name " + name;
   10612           1 :             return false;
   10613             :         }
   10614             : 
   10615           3 :         osMappingTableName = poRelationship->GetMappingTableName();
   10616             :     }
   10617             : 
   10618             :     // DeleteLayerCommon will delete existing relationship objects, so we can't
   10619             :     // refer to poRelationship or any of its members previously obtained here
   10620           3 :     if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
   10621             :     {
   10622             :         failureReason =
   10623           0 :             "Could not remove mapping layer name " + osMappingTableName;
   10624             : 
   10625             :         // relationships may have been left in an inconsistent state -- reload
   10626             :         // them now
   10627           0 :         ClearCachedRelationships();
   10628           0 :         LoadRelationships();
   10629           0 :         return false;
   10630             :     }
   10631             : 
   10632           3 :     ClearCachedRelationships();
   10633           3 :     LoadRelationships();
   10634           3 :     return true;
   10635             : }
   10636             : 
   10637             : /************************************************************************/
   10638             : /*                        UpdateRelationship()                          */
   10639             : /************************************************************************/
   10640             : 
   10641           6 : bool GDALGeoPackageDataset::UpdateRelationship(
   10642             :     std::unique_ptr<GDALRelationship> &&relationship,
   10643             :     std::string &failureReason)
   10644             : {
   10645           6 :     if (eAccess != GA_Update)
   10646             :     {
   10647           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10648             :                  "UpdateRelationship() not supported on read-only dataset");
   10649           0 :         return false;
   10650             :     }
   10651             : 
   10652             :     // ensure relationships are up to date before we try to update one
   10653           6 :     ClearCachedRelationships();
   10654           6 :     LoadRelationships();
   10655             : 
   10656           6 :     const std::string &osRelationshipName = relationship->GetName();
   10657           6 :     const std::string &osLeftTableName = relationship->GetLeftTableName();
   10658           6 :     const std::string &osRightTableName = relationship->GetRightTableName();
   10659           6 :     const std::string &osMappingTableName = relationship->GetMappingTableName();
   10660           6 :     const auto &aosLeftTableFields = relationship->GetLeftTableFields();
   10661           6 :     const auto &aosRightTableFields = relationship->GetRightTableFields();
   10662             : 
   10663             :     // sanity checks
   10664             :     {
   10665             :         const GDALRelationship *poExistingRelationship =
   10666           6 :             GetRelationship(osRelationshipName);
   10667           6 :         if (poExistingRelationship == nullptr)
   10668             :         {
   10669             :             failureReason =
   10670           1 :                 "The relationship should already exist to be updated";
   10671           1 :             return false;
   10672             :         }
   10673             : 
   10674           5 :         if (!ValidateRelationship(relationship.get(), failureReason))
   10675             :         {
   10676           2 :             return false;
   10677             :         }
   10678             : 
   10679             :         // we don't permit changes to the participating tables
   10680           3 :         if (osLeftTableName != poExistingRelationship->GetLeftTableName())
   10681             :         {
   10682           0 :             failureReason = ("Cannot change base table from " +
   10683           0 :                              poExistingRelationship->GetLeftTableName() +
   10684           0 :                              " to " + osLeftTableName)
   10685           0 :                                 .c_str();
   10686           0 :             return false;
   10687             :         }
   10688           3 :         if (osRightTableName != poExistingRelationship->GetRightTableName())
   10689             :         {
   10690           0 :             failureReason = ("Cannot change related table from " +
   10691           0 :                              poExistingRelationship->GetRightTableName() +
   10692           0 :                              " to " + osRightTableName)
   10693           0 :                                 .c_str();
   10694           0 :             return false;
   10695             :         }
   10696           3 :         if (osMappingTableName != poExistingRelationship->GetMappingTableName())
   10697             :         {
   10698           0 :             failureReason = ("Cannot change mapping table from " +
   10699           0 :                              poExistingRelationship->GetMappingTableName() +
   10700           0 :                              " to " + osMappingTableName)
   10701           0 :                                 .c_str();
   10702           0 :             return false;
   10703             :         }
   10704             :     }
   10705             : 
   10706           6 :     std::string osRelatedTableType = relationship->GetRelatedTableType();
   10707           3 :     if (osRelatedTableType.empty())
   10708             :     {
   10709           0 :         osRelatedTableType = "features";
   10710             :     }
   10711             : 
   10712           3 :     char *pszSQL = sqlite3_mprintf(
   10713             :         "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
   10714             :         osMappingTableName.c_str());
   10715           3 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
   10716           3 :     sqlite3_free(pszSQL);
   10717           3 :     if (eErr != OGRERR_NONE)
   10718             :     {
   10719             :         failureReason =
   10720           0 :             "Could not delete old relationship from gpkgext_relations";
   10721           0 :         return false;
   10722             :     }
   10723             : 
   10724           9 :     pszSQL = sqlite3_mprintf(
   10725             :         "INSERT INTO gpkgext_relations "
   10726             :         "(base_table_name,base_primary_column,related_table_name,related_"
   10727             :         "primary_column,relation_name,mapping_table_name) "
   10728             :         "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
   10729           3 :         osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
   10730           3 :         osRightTableName.c_str(), aosRightTableFields[0].c_str(),
   10731             :         osRelatedTableType.c_str(), osMappingTableName.c_str());
   10732           3 :     eErr = SQLCommand(hDB, pszSQL);
   10733           3 :     sqlite3_free(pszSQL);
   10734           3 :     if (eErr != OGRERR_NONE)
   10735             :     {
   10736             :         failureReason =
   10737           0 :             "Could not insert updated relationship into gpkgext_relations";
   10738           0 :         return false;
   10739             :     }
   10740             : 
   10741           3 :     ClearCachedRelationships();
   10742           3 :     LoadRelationships();
   10743           3 :     return true;
   10744             : }
   10745             : 
   10746             : /************************************************************************/
   10747             : /*                    GetSqliteMasterContent()                          */
   10748             : /************************************************************************/
   10749             : 
   10750             : const std::vector<SQLSqliteMasterContent> &
   10751           2 : GDALGeoPackageDataset::GetSqliteMasterContent()
   10752             : {
   10753           2 :     if (m_aoSqliteMasterContent.empty())
   10754             :     {
   10755             :         auto oResultTable =
   10756           2 :             SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
   10757           1 :         if (oResultTable)
   10758             :         {
   10759          58 :             for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
   10760             :             {
   10761         114 :                 SQLSqliteMasterContent row;
   10762          57 :                 const char *pszSQL = oResultTable->GetValue(0, rowCnt);
   10763          57 :                 row.osSQL = pszSQL ? pszSQL : "";
   10764          57 :                 const char *pszType = oResultTable->GetValue(1, rowCnt);
   10765          57 :                 row.osType = pszType ? pszType : "";
   10766          57 :                 const char *pszTableName = oResultTable->GetValue(2, rowCnt);
   10767          57 :                 row.osTableName = pszTableName ? pszTableName : "";
   10768          57 :                 m_aoSqliteMasterContent.emplace_back(std::move(row));
   10769             :             }
   10770             :         }
   10771             :     }
   10772           2 :     return m_aoSqliteMasterContent;
   10773             : }

Generated by: LCOV version 1.14